<?PHP
/*
 * $Id: barge-utils.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://:barge.googlecode.com/
 */
namespace barge\utils;

use \DateTime;
use \ArrayAccess;
use \ArrayIterator;
use \Closure;
use \Exception;
use \IteratorAggregate;
use \Countable;
use \SimpleXMLElement;
use \ReflectionClass;
use \ReflectionObject;
use \ReflectionProperty;
use \ReflectionMethod;
use \ReflectionFunction;
use \ReflectionException;

use barge\System;
use barge\ReflectionAnnotation;
use barge\io\File;
use barge\io\FileNotFoundException;
use barge\logging\LogFactory;

/**
 * Nested exception.
 *
 * @version 1.0 2009/03/28
 * @since 1.2.2 2009/03/28
 * @see Exception
 */
class NestedException extends Exception
{
	/**
	 * @var Exception
	 */
	private $cause;

	/**
	 * @param string $message
	 * @param Exception $cause
	 */
	public function __construct($message, Exception $cause = null)
	{
		$this->cause = $cause;
		parent::__construct($message);
	}

	/**
	 * @return Exception
	 */
	public function getCause()
	{
		return $this->cause;
	}

}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class ExceptionUtils
{
	/**
	 * トレースをJAVA風のトレースに変換する
	 * @param Exception $exception
	 * @return string
	 * @since 1.0 2006/10/19
	 */
	public static function getTraceAsString(Exception $exception)
	{
		$exception->getCode();
		$bt = $exception->getTrace();
		$ret = ObjectUtils::getClass($exception) . ': ' . $exception->getMessage() . "\n";

		$caller = array('file'=>basename($exception->getFile()), 'line'=>$exception->getLine());
		$stack = null;
		foreach ($bt as $stack) {
			$filename = basename(isset($caller['file']) ? $caller['file'] : '');
			$ret .= sprintf("    at %s%s%s(%s:%s)\n",
				isset($stack['class']) ? $stack['class'] : '',
				isset($stack['type']) ? $stack['type'] : '',
				$stack['function'],
				$filename,
				isset($caller['line']) ? $caller['line'] : '');
			$caller = $stack;
		}
		$filename = basename(isset($stack['file']) ? $stack['file'] : '');
		$ret .= sprintf("    at <main>(%s:%s)\n",
				$filename,
				isset($stack['line']) ? $stack['line'] : '');
		if ($exception instanceof NestedException) {
			/* @var $exception NestedException */
			if ($exception->getCause() !== null) {
				$ret .= 'Caused by: ';
				$ret .= self::getTraceAsString($exception->getCause());
			}
		}
		return $ret;
	}
}

class ParseException extends NestedException {}
class IllegalAccessException extends NestedException {}
class IllegalArgumentException extends NestedException {}
class IndexOutOfBoundsException extends NestedException {}
class UndefinedConfigException extends NestedException {}

/**
 * 設定ファイル取得インターフェース
 * @author Kenji Namba
 * @version 1.0 2008/09/18
 * @see IteratorAggregate
 * @see Collection
 * @see Countable
 */
class Config implements IteratorAggregate, Collection, Countable
{
	/**
	 * @var array
	 */
	private $items;

	/**
	 * コンストラクタ
	 * @param ArrayList|array $array
	 */
	public function __construct($array = null)
	{
		if ($array === null) {
		} else if (is_array($array)) {
			$this->items = $array;
		} else if ($array instanceof Collection) {
			$this->items = $array->toArray();
		} else {
			$message = "Illegual argument.";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * @param ... プロパティーキー、複数指定することでネストしたプロパティーを指定できます
	 * @throws IllegalArgumentException 現在の配列をスカラー値で上書きした場合スローされます
	 */
	public function removeProperty(/*...*/)
	{
		$args = func_get_args();
		if (count($args) < 1) {
			$message = "パラメータが少なすぎます。";
			throw new IllegalArgumentException($message);
		}
		$arglen = count($args);
		$key = $args[$arglen - 1];

		$len = $arglen - 1;
		$config =& $this->items;
		for ($i = 0; $i < $len; $i++) {
			if (!isset($config[$args[$i]])) {
				$config[$args[$i]] = array();
			}
			if (is_array($config[$args[$i]])) {
				$config =& $config[$args[$i]];
			} else {
				$message = "Invalidate array";
				throw new IllegalArgumentException($message);
			}
		}

		if (is_array($config)) {
			unset($config[$key]);
		} else {
			$message = "Invalidate array";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * @param string $path
	 * @param string $value
	 * @throws IllegalArgumentException 現在の配列をスカラー値で上書きした場合スローされます
	 */
	public function setProperty($path, $value)
	{
		$args = explode('/', $path);
		$arglen = count($args);
		$key = $args[$arglen - 1];
//		$value = $args[$arglen - 1];

		$len = $arglen - 1;
		$config =& $this->items;
		for ($i = 0; $i < $len; $i++) {
			if (!isset($config[$args[$i]])) {
				$config[$args[$i]] = array();
			}
			if (is_array($config[$args[$i]])) {
				$config =& $config[$args[$i]];
			} else {
				$message = "Invalidate array";
				throw new IllegalArgumentException($message);
			}
		}

		if (is_array($config)) {
			$config[$key] = $value;
		} else {
			$message = "Invalidate array";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * @param string $path
	 * @return mixed
	 */
	public function getBoolProperty($path)
	{
		return in_array($this->getProperty($path), array('yes', 'true', 'on'));
	}

	/**
	 * @param string $path
	 * @param string $default
	 * @return mixed
	 */
	public function getProperty($path, $default = null)
	{
		if ($path[0] === '/') {
			$path = substr($path, 1);
		}
//		$reflection = new ReflectionMethod('barge\utils\Config', 'getProperty');
//		$value = $reflection->invokeArgs(self::$config, explode('/', $path));;
//		if ($value !== null) {
//			return ($value !== null ? $value : $value);
//		} else {
//			return $value;
//		}

		$args = explode('/', $path);
		$config = $this->items;
		foreach ($args as &$arg) {
			if (isset($config[$arg])) {
				$config = $config[$arg];
			} else {
				$config = null;
				break;
			}
		}

		if ($default !== null && $config === null) {
			return $default;
		} else {
			return is_array($config) ?
				new self($config) : $config;
		}
	}

	/**
	 * @return array
	 */
	public function toArray()
	{
		return $this->items;
	}

	/**
	 * @return Config $config
	 */
	public function merge(Config $config)
	{
		$this->items = array_merge_recursive($config->toArray(), $this->items);
	}

	/**
	 * Required definition of interface IteratorAggregate
	 *
	 * @return Iterator
	 * @since 1.0 2006/09/11
	 * @see IteratorAggregate#getIterator
	 */
	public function getIterator()
	{
		return new ConfigIterator($this->items);
	}

	/**
	 * 要素の個数を返します
	 *
	 * @return int
	 * @see Countable#count
	 * @since 2008/07/26
	 */
	public function count()
	{
		return count($this->items);
	}

	/**
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public function isEmpty()
	{
		return count($this->items) === 0;
	}

	/**
	 * @return int
	 * @since 1.0 2006/09/11
	 */
	public function size()
	{
		return count($this->items);
	}
}

/**
 * 設定ファイルのためのイテレータ
 *
 * @see ArrayIterator
 * @author Kenji Namba
 * @version 1.0 2008/09/18
 * @see Config
 */
class ConfigIterator extends ArrayIterator
{
	/**
	 * @see ArrayIterator#current
	 */
	public function current()
	{
		$obj = parent::current();
		return is_array($obj) ? new Config($obj) : $obj;
	}
}

///**
// * 設定ファイル取得インターフェース
// * @author Kenji Namba
// * @version 1.0 2008/09/18
// */
//interface Config
//{
//	public function getProperty(/*...*/);
//
//	/**
//	 * @param Config $config
//	 * @return Config
//	 */
//	public function merge(Config $config);
//
//}

/**
 * <pre>
 * $config = XMLConfig::load('src/sample.xml');
 * </pre>
 *
 * @author Kenji Namba
 * @version 1.0 2008/09/18
 */
class XMLConfig
{
	/**
	 * XMLで記述された設定ファイルをロードします。
	 *
	 * @param $xmlpath XMLファイルのパス
	 * @return Config
	 * @throw FileNotFoundException
	 */
	public static function load($xmlpath)
	{
		if (!file_exists($xmlpath)) {
			$message = "XML ファイル '{$xmlpath}' は存在しません。";
			throw new FileNotFoundException($message);
		}

		// FIXME : ここに LOG 出力を追加すると再帰する
		$workdir = System::getWorkDir();
		$workfile = $workdir .'/'. md5($xmlpath).'.xml.ser';
		$config = null;
		if (file_exists($workfile) && filemtime($xmlpath) === filemtime($workfile)) {
			$config = unserialize(file_get_contents($workfile));
		} else {
			//$xml = simplexml_load_file($xmlpath, 'SimpleXMLElement', LIBXML_NOBLANKS | LIBXML_NOCDATA | LIBXML_NOENT);
			$xml = @simplexml_load_file($xmlpath);
			if (!$xml) {
				$message = "XMLファイル '{$xmlpath}' のパースに失敗しました。";
				throw new ParseException($message);
			}

			//$config =& self::toArray($xml);
			$config = self::toArray($xml);
			file_put_contents($workfile, serialize($config));
			touch($workfile, filemtime($xmlpath));
		}
		return new Config($config);
	}

	/**
	 * @param SimpleXMLElement $element
	 * @param $ignores
	 * @return array
	 */
	protected static function toArray(SimpleXMLElement $element, $ignores = false)
	{
		//print_r($element);
		if (!$element->children()) {
			if (is_numeric($element)) {
				return floatval((string)$element);
			} else {
				return (string)$element;
			}
		} else if ($ignores && isset($element[0]) && count($element) === 0) {
			return (string)$element[0];
		}

		$list = array();
		foreach ($element->attributes() as $key => $value) {
			if ($key === 'id') continue;
			$list[$key] = (string)$value;
		}
		foreach ($element->children() as $name => $child) {
			/* @var $child SimpleXMLElement */
			$ignore = false;
			if (isset($child->attributes()->id)) {
				$name = (string)$child->attributes()->id;
				if (count($child->attributes()) === 1) {
					$ignore = true;
				}
			}
			$list[$name] = self::toArray($child, $ignore);
//			if ($strict) {
//				if (! isset($list[$name])) {
//					$list[$name] = array();
//				}
//				array_push($list[$name], self::toArray($child, $strict, $ignore));
//			} else {
//				$list[$name] = self::toArray($child, $strict, $ignore);
//			}
		}
		return $list;
	}
}

/**
 * INI ファイルによる設定クラス
 * @author Kenji Namba
 * @version 1.0 2008/09/18
 */
class INIConfig
{
	/**
	 * @param $xmlpath XMLファイルのパス
	 * @return Config
	 * @throw new FileNotFoundException ファイルが存在しない　
	 */
	public static function load($inipath)
	{
		if (!File::exists($inipath)) {
			$message = "INI ファイル '{$inipath}' は存在しません。";
			throw new FileNotFoundException($message);
		}
		return new Config(self::toArray(parse_ini_file($inipath, true)));
	}

	/**
	 * parse_ini_file ファイルの結果をネストした設定情報に変換します
	 *
	 * @param array $array
	 * @return array
	 */
	private static function toArray(array $array)
	{
		// [aaaaa:bbbb]
		$ret = array();
		foreach ($array as $key => $value) {
			if (strpos($key, ':') !== false) {
				$entries = explode(':', $key);
				$current =& $ret;
				foreach ($entries as $entry) {
					if (!isset($current[$entry])) {
						$current[$entry] = array();
					}
					$current =& $current[$entry];
				}
				$current = $value;
			} else {
				$ret[$key] = $value;
			}
		}
		return $ret;
	}
}

/**
 * プロパティーアクセスサポート
 * <p/>
 * このクラスは、ゲッター／セッターを介し外部からプロパティーにアクセスする機能をサブクラスに
 * 提供します。またプロパティーアクセスをサブクラスで定義されているプロパティーのみに限定しま
 * す。
 * <pre>
 * class MyClass extends Bean
 * {
 *     protected $name;
 *     public function getName()
 *     {
 *       return $this->name;
 *     }
 *     public function setName($value)
 *     {
 *       $this->name = $value;
 *     }
 * }
 * $obj = new MyClass();
 * echo $obj->name;		// MyClass::getName が呼ばれる
 * echo $obj['name'};	// MyClass::getName が呼ばれる
 * echo $obj->age;		// IllegalAccessExceptionがスローされる
 * $obj->name = 1;		// MyClass::setName が呼ばれる
 * </pre>
 * サブクラスで定義するプロパティーは<code>protected</code>以上のスコープで定義するしなけ
 * ればなりません。
 *
 * @version 1.0
 * @author Kenji Namba
 * @see ArrayAccess
 * @since Barge 1.0 2008/02/12
 */
abstract class Bean implements ArrayAccess
{
	/**
	 * 取得アクセスのためのオーバーロード
	 * @param string $name
	 * @return mixed
	 */
	public function __get($name)
	{
		return BeanUtils::getProperty($this, $name);
//		$getter = $this->getGetterMethod($name);
//		if (method_exists($this, $getter)) {
//			return $this->$getter();
//		} else {
//			$message = "クラス '".get_class($this)."' にはプロパティー '{$name}' は定義されていません。";
//			throw new IllegalAccessException($message);
//		}
	}

	/**
	 * @param mixed $offset
	 * @return bool
	 * @see ArrayAccess::offsetExists()
	 */
	public function offsetExists($offset)
	{
		$descriptor = BeanUtils::getPropertyDescriptor($this);
		return $descriptor->isReadable();
//		return BeanUtils::hasReadableProperty($this, $offset);
	}

	/**
	 * @param mixed $offset
	 * @return mixed
	 * @see ArrayAccess::offsetGet()
	 */
	public function offsetGet($offset)
	{
		return $this->__get($offset);
	}

	/**
	 * @param mixed $offset
	 * @param mixed $value
	 * @see ArrayAccess::offsetSet()
	 */
	public function offsetSet($offset, $value)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess::offsetUnset()
	 */
	public function offsetUnset($offset)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * 設定アクセスのためのオーバーロード
	 * @param string $name プロパティー名
	 * @param mixed $value 設定値
	 * @throws IllegalAccessException 指定されるプロパティー名が存在しない場合にスロー
	 * 			される
	 */
	public function __set($name, $value)
	{
		BeanUtils::setProperty($this, $name, $value);
	}

}

/**
 * プロパティーアノテーション
 * @version 1.0 2009/11/28
 * @since 1.4 2009/11/28
 */
interface Property
{
	public function name();
}

/**
 * <pre>
 * $stopwatch = new Stopwatch();
 * $stopwatch->start();
 * echo $stopwatch->sprint();
 * </pre>
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class Stopwatch
{
	/**
	 * @var ArrayList
	 * @since 1.0 2006/11/01
	 */
	private $stack = array();

	/**
	 * Start stopwatch.
	 * @since 1.0 2006/11/01
	 */
	public function start()
	{
		$this->stack = array();
		$this->stack[] = microtime(true);
	}

	/**
	 * @return float 秒単位の実数
	 * @since 1.0 2006/11/01
	 */
	public function sprint()
	{
// FIXME : start していない時に呼び出された場合
		$this->stack[] = microtime(true);
		$size = count($this->stack);
		return $this->stack[$size - 1] -
		$this->stack[$size - 2];
	}

	/**
	 * 最初の開始から現在の時間を返す
	 * @return float
	 * @since 1.3 2009/04/11
	 */
	public function total()
	{
// FIXME : start していない時に呼び出された場合
		$size = count($this->stack);
		return $this->stack[$size - 1] -
		$this->stack[0];
	}
}

/**
 *
 * @author Kenji Namba
 * @version 1.1
 * @since Barge 1.1 2006/08/12
 */
class BeanUtils
{
	/**
	 * Chache
	 * @since 1.4 2009/11/19
	 */
	private static $cache = array();

	/**
	 *
	 * @param object $bean
	 * @param HashMap|array $map HashMap or named array.
	 * @since 1.0 2006/08/12
	 */
	public static function copyProperties($bean, $map)
	{
		// TODO :
		$map = (($map instanceof HashMap) ? $map->toArray() : $map);
		foreach ($map as $key => $value) {
			self::setProperty($bean, $key, $value);
		}
	}

	/**
	 * @param object|HashMap $object
	 * @param string $property
	 * @param mixed $value
	 * @since 1.0 2006/08/12
	 */
	public static function setProperty($object, $property, $value)
	{
		$p = strrpos($property, '.');
		if ($p !== false) {
			$target = substr($property, 0, $p);
			$property = substr($property, $p + 1);

			$object = self::getProperty($object, $target);
		}

		if ($object instanceof HashMap) {
			$object->put($property, $value);
		} else {
			/* @var $map HashMap */
			$map = self::loadPropertyDescriptorMap($object);
			/* @var $propertyDescriptor PropertyDescriptor */
			if (($propertyDescriptor = $map->get($property)) === null || !$propertyDescriptor->isWritable()) {
				$message = "クラス '".get_class($object)."' にアクセス可能なプロパティー '{$property}' は定義されていません。'
					. 'public プロパティーまたは、アクセス可能な書き込みプロパティーが必要です。";
				throw new ReflectionException($message);
			}
			$propertyDescriptor->getWriter()->write($object, $value);
//			$refrectionObject = new ReflectionObject($object);
//			if ($refrectionObject->hasProperty($property) && $refrectionObject->getProperty($property)->isPublic()) {
//				/* @var $reflectionProperty ReflectionProperty */
//				$reflectionProperty = $refrectionObject->getProperty($property);
//				$reflectionProperty->setValue($object, $value);
//			} else if ($refrectionObject->hasMethod('set'.ucfirst($property))) {
//				/* @var $reflectionMethod ReflectionMethod */
//				$reflectionMethod = $refrectionObject->getMethod('set'.ucfirst($property));
//				if ($reflectionMethod->isPublic() &&
//					$reflectionMethod->getNumberOfParameters() === 1) {
//					try {
//						$reflectionMethod->invokeArgs($object, array($value));
//					} catch (ReflectionException $e) {
//						$message = "クラス '".get_class($object)."' のプロパティー '{$property}' 書き込み時に例外が発生しました";
//						throw new ReflectionException($message);
//					}
//				}
//			} else {
//				$message = "クラス '".get_class($object)."' にアクセス可能なプロパティー '{$property}' は定義されていません。public プロパティーまたは、アクセス可能な書き込みプロパティーが必要です。";
//				throw new ReflectionException($message);
//			}
		}
	}

//	/**
//	 * @param object $object
//	 * @param string $property
//	 */
//	public static function hasReadableProperty($object, $property)
//	{
//		$prefix = ucfirst($property);
//		$reflection = new ReflectionObject($object);
//		if ($reflection->hasMethod('get'.$prefix)) {
//			$method = $reflection->getMethod('get'.$prefix);
//			/* @var $method ReflectionMethod */
//			if ($method->isPublic()) {
//				return true;
//			}
//		}
//		if ($reflection->hasMethod('is'.$prefix)) {
//			$method = $reflection->hasMethod('is'.$prefix);
//			if ($method->isPublic()) {
//				return true;
//			}
//		}
//		return false;
//	}

//	/**
//	 * @param object $object
//	 * @param string $property
//	 */
//	public static function hasWriteableProperty($object, $property)
//	{
//		$prefix = ucfirst($property);
//		$reflection = new ReflectionObject($object);
//		if ($reflection->hasMethod('set'.$prefix)) {
//			$method = $reflection->getMethod('set'.$prefix);
//			/* @var $method ReflectionMethod */
//			if ($method->isPublic()) {
//				return true;
//			}
//		}
//		return false;
//	}

	/**
	 * オブジェクトのプロパティー値を取得します。プロパティーへのアクセスは、適切なアクセッサーが定義されている
	 * 場合は、アクセッサー経由で行われます。アクセッサーが定義されていない場合は、プロパティーへ直接アクセス
	 * を試みます。プロパティーへの参照は、ドット記述が可能です。
	 *
	 * <pre>
	 * BeanUtils.getProperty($obj, 'id');
	 * BeanUtils.getProperty($obj, 'user.id');
	 * </pre>
	 *
	 * @param object $object
	 * @param string $property プロパティー名
	 * @return mixed
	 * @since 1.0 2008/08/12
	 * @throws ReflectionException プロパティーが存在しない、またはアクセス不可能
	 */
	public static function getProperty($object, $property)
	{
		$properties = explode('.', $property);
		$value = null;

		$i = 0;
		foreach ($properties as $prop) {
			if ($i > 0) {
				$object = $value;
			}
			if ($object instanceof HashMap) {
				$value = $object->get($prop);
				if ($value === null) break;
			} else {
				/* @var $map HashMap */
				$map = self::loadPropertyDescriptorMap($object);
				if (($propertyDescriptor = $map->get($prop)) === null || !$propertyDescriptor->isReadable()) {
					$message = "クラス '".get_class($object)."' にアクセス可能なプロパティー '{$prop}' は定義されていません。"
						."有効なアクセッサーまたはpublicプロパティーが必要です。";
					throw new ReflectionException($message);
				}
				$value  = $propertyDescriptor->getReader()->read($object);
			}
			$i++;
		}
		return $value;
	}

	/**
	 * 指定されたプロパティーのプロパティー記述子を返す、このメソッドはプロパティーへ
	 * 読み込み・書き込みアクセス可能な場合のみ、プロパティー記述子を返します。プロパティーが
	 * ない場合は、<code>null</code>を返します。
	 *
	 * @param object $obj
	 * @param string $property
	 * @return PropertyDescriptor
	 * @since 1.3 2009/04/08
	 */
	public static function getPropertyDescriptor($obj, $property)
	{
		$map = self::loadPropertyDescriptorMap($obj);
		return $map->get($property);
//		$class = get_class($obj);
//		if (isset($cache[$class]) &&
//			isset($cache[$class][$property])) {
//			return $cache[$class][$property];
//		}

//		$refrectionObject = new ReflectionObject($obj);
//		$readtype = null;
//		$writable = false;
//		$readprefix = null;
//		if ($refrectionObject->hasProperty($property)) {
//			/** @var $reflectionProperty ReflectionProperty */
//			$reflectionProperty = $refrectionObject->getProperty($property);
//			if ($reflectionProperty->isPublic()) {
//				$readtype = 'field';
//				$writable = true;
//			}
//		}
//		// プロパティーがpublicでなければアクセッサーを探す
//		if ($readtype === null) {
//			if ($refrectionObject->hasMethod('get'.ucfirst($property))) {
//				/** @var $reflectionMethod ReflectionMethod */
//				$reflectionMethod = $refrectionObject->getMethod('get'.ucfirst($property));
//				if ($reflectionMethod->isPublic() &&
//					$reflectionMethod->getNumberOfRequiredParameters() === 0) {
//					$readprefix = 'get';
//					$readtype = 'accessor';
//				}
//			} else if ($refrectionObject->hasMethod('is'.ucfirst($property))) {
//				/* @var $reflectionMethod ReflectionMethod */
//				$reflectionMethod = $refrectionObject->getMethod('is'.ucfirst($property));
//				if ($reflectionMethod->isPublic() &&
//					$reflectionMethod->getNumberOfRequiredParameters() === 0) {
//					$readprefix = 'is';
//					$readtype = 'accessor';
//				}
//			}
//			if ($refrectionObject->hasMethod('set'.ucfirst($property))) {
//				/* @var $reflectionMethod ReflectionMethod */
//				$reflectionMethod = $refrectionObject->getMethod('set'.ucfirst($property));
//				if ($reflectionMethod->isPublic() &&
//					$reflectionMethod->getNumberOfParameters() === 1) {
//					$writable = true;
//				}
//			}
//		}
//		if ($readtype !== null || $writable) {
//	 		$obj = new PropertyDescriptor($refrectionObject, $property,
//	 				$readtype, $readprefix, $writable);
////	 		$cache[$class][$property] = $obj;
//	 		return $obj;
//		} else {
//			return null;
//		}
	}

	/**
	 * 読み込み・または書き込みアクセス可能なプロパティーのリストを返します
	 *
	 * @param object $obj
	 * @return ArrayList {@link PropertyDescriptor}の一覧
	 * @since 1.0 2008/08/12
	 */
	public static function getPropertyDescriptors($obj)
	{
		return self::loadPropertyDescriptorMap($obj)->values();
	}

	/**
	 * @param object|string $obj
	 * @return HashMap
	 * @since 1.0 2008/08/12
	 */
	private static function loadPropertyDescriptorMap($obj)
	{
		$class = is_object($obj) ? get_class($obj) : $obj;
		if (isset(self::$cache[$class])) {
			return self::$cache[$class];
		}

		// プロパティーを検索する
		$ref = new ReflectionClass($class);
		$ax = new ReflectionAnnotation($class);
		$propertyMap = array();
		$properties = $ref->getProperties();
		$map = new HashMap();
		foreach ($properties as $property) {	/* @var $property ReflectionProperty */
			if ($property->isStatic())
				continue;
			$name = $property->getName();
			$canonical = $name;
			/* @var $annotation Property*/
			$annotation = $ax->getPropertyAnnotation($name, 'barge\utils\Property');
			if ($annotation !== null && System::isNotEmpty($annotation->name())) {
				$propertyMap[$canonical] = $annotation->name();
				$name = $propertyMap[$canonical];
			}
			if ($property->isPublic()) {
				$map->put($name, new PropertyDescriptor($ref, $name, $canonical,
					PropertyDescriptor::PROPERTY, null, PropertyDescriptor::PROPERTY));
			}
		}

		// is/get/set メソッドを検索する
		$methods = $ref->getMethods(ReflectionMethod::IS_PUBLIC);
//		$methodMap = new HashMap();
		foreach ($methods as $method) {
			/* @var $method ReflectionMethod */
			$name = $method->getName();
			$property = null;
			$prefix = null;
			if (substr($name, 0, 3) === 'get') {
				$property = self::lcfirst(substr($name, 3));
//				$prefix = 'get';
			} else if (substr($name, 0, 3) === 'set') {
				$property = self::lcfirst(substr($name, 3));
//				$prefix = 'set';
			} else if (substr($name, 0, 2) === 'is') {
				$property = self::lcfirst(substr($name, 2));
//				$prefix = 'is';
			} else {
				continue;
			}
			$canonical = $property;
			if (isset($propertyMap[$property])) {
				$property = $propertyMap[$property];
			}
			if (!$map->contains($property)) {
				$readable = false;
				$writable = false;
				$readprefix = null;
				// 読み込みプロパティー
				if ($ref->hasMethod('get'.$canonical) &&
					$ref->getMethod('get'.$canonical)->getNumberOfParameters() === 0) {
					$readprefix = 'get';
					$readable = true;
				} else if ($ref->hasMethod('is'.$canonical) &&
						$ref->getMethod('is'.$canonical)->getNumberOfParameters() === 0) {
					$readprefix = 'is';
					$readable = true;
				}
				// 書き込みプロパティー
				if ($ref->hasMethod('set'.$canonical) &&
					$ref->getMethod('set'.$canonical)->getNumberOfParameters() === 1) {
					$writable = true;
				}

//				if ($prefix === 'get' && $method->getNumberOfRequiredParameters() === 0) {
//					$readable = true;
//					$readprefix = 'get';
//					$suffix = substr($name, 3);
//					if ($refrectionObject->hasMethod('set'.ucfirst($property)) &&
//						$refrectionObject->getMethod('set'.ucfirst($property))->getNumberOfParameters() === 1) {
//						$writable = true;
//					}
//				} else if ($prefix === 'is' && $method->getNumberOfRequiredParameters() === 0) {
//					$readable = true;
//					$readprefix = 'is';
//					$suffix = substr($name, 2);
//					if ($refrectionObject->hasMethod('set'.ucfirst($property)) &&
//						$refrectionObject->getMethod('set'.ucfirst($property))->getNumberOfParameters() === 1) {
//						$writable = true;
//					}
//				} else if ($prefix === 'set' && $method->getNumberOfRequiredParameters() === 1) {
//					$writable = true;
//					$suffix = substr($name, 3);
//					if ($refrectionObject->hasMethod('get'.ucfirst($property)) &&
//						$refrectionObject->getMethod('get'.ucfirst($property))->getNumberOfRequiredParameters() === 0) {
//						$readable = true;
//						$readprefix = 'set';
//					} else if ($refrectionObject->hasMethod('is'.ucfirst($property)) &&
//						$refrectionObject->getMethod('is'.ucfirst($property))->getNumberOfRequiredParameters() === 0) {
//						$readable = true;
//						$readprefix = 'is';
//					}
//				}
				if ($readable || $writable) {
					$map->put($property, new PropertyDescriptor($ref,
						$property, $canonical, $readable ? PropertyDescriptor::ACCSESSOR : 0, $readprefix, $writable ? PropertyDescriptor::ACCSESSOR : 0));
				}
			}
		}

		// Create cache.
		self::$cache[$class] = $map;
		return $map;
	}

	private static function lcfirst($name)
	{
		return strtolower($name[0]) . substr($name, 1);
	}

	/**
	 * オブジェクトからマップを作成します、このメソッドは、指定されたオブジェクトのをリフレクションし、有効な公開されているプロパティー
	 * の値からマップを作成して返します。
	 *
	 * @param object $obj
	 * @param ArrayList $list プロパティー化するプロパティーの一覧
	 * @param bool $recursive オブジェクトを再帰的に変換するかどうか
	 * @return HashMap
	 * @since 1.0 2008/08/12
	 */
	public static function toMap($obj, $recursive = false)
	{
		if ($obj === null) {
			$message = 'Invalidate argument null';
			throw new IllegalArgumentException($message);
		}
		// TODO :
// FIXME : 再帰
		$refrect = new ReflectionObject($obj);
		$methods = $refrect->getMethods(ReflectionMethod::IS_PUBLIC);
		$map = new HashMap();
		foreach ($methods as $method) {
			/* @var $method ReflectionMethod */
			$name = $method->getName();
			if ((substr($name, 0, 3) === 'get' || substr($name, 0, 2) === 'is') &&
				$method->getNumberOfParameters() === 0) {
				$property = strtolower(substr($name, 3, 1)).substr($name, 4);
				$value = $method->invoke($obj);
				if (is_object($value)) {
					if ($recursive) {
						$map->put($property, self::toMap($value, true));
					}
				} else {
					$map->put($property, $value);
				}
			}
		}
		return $map;
	}
}

/**
 * プロパティー読み込みインターフェース
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
interface PropertyReader
{
	public function read($target);
}

/**
 * プロパティー書き込みインターフェース
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
interface PropertyWriter
{
	public function write($target, $vaue);
}

/**
 * パブリックプロパティーへ直接書き込むアクセッサー
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
class AccessorPropertyReader implements PropertyReader
{
	/**
	 * @var ReflectionMethod
	 */
	private $method;

	/**
	 * @param ReflectionMethod $method
	 */
	public function __construct(ReflectionMethod $method)
	{
		$this->method = $method;
	}

	/**
	 * @param $target
	 */
	public function read($target)
	{
		return $this->method->invoke($target, array());
	}
}

/**
 * パブリックプロパティーへ直接書き込むアクセッサー
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
class AccessorPropertyWriter implements PropertyWriter
{
	/**
	 * @var ReflectionMethod
	 */
	private $method;

	/**
	 * @param ReflectionMethod $method
	 */
	public function __construct(ReflectionMethod $method)
	{
		$this->method = $method;
	}

	/**
	 * @param $target
	 * @param $value
	 */
	public function write($target, $value)
	{
		$this->method->invoke($target, $value);
	}
}


/**
 * パブリックプロパティーへ直接書き込むアクセッサー
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
class DirectPropertyReadWriter implements PropertyReader, PropertyWriter
{
	/**
	 * @var ReflectionProperty
	 */
	private $property;

	public function __construct(ReflectionProperty $property)
	{
		$this->property = $property;
	}

	/**
	 * @param $target
	 */
	public function read($target)
	{
		return $this->property->getValue($target);
	}

	/**
	 * @param $target
	 * @param $vaue
	 */
	public function write($target, $vaue)
	{
		$this->property->setValue($target, $vaue);
	}
}

/**
 * プロパティー記述子
 *
 * @version 1.0
 * @author Kenji Namba
 * @since barge 1.0 2008/09/11
 */
class PropertyDescriptor
{
//	const IMPOSSIBLE = 0;
	const PROPERTY = 1;
	const ACCSESSOR = 2;

	/**
	 * @var ReflectionObject
	 */
	private $reflectionClass;

	/**
	 * @var string
	 */
	private $property;

	/**
	 * Canonical property name.
	 * @var string
	 */
	private $canonical;

	/**
	 * @var int
	 */
	private $readable = 0;

	/**
	 * @var string
	 */
	private $readPrefix = 'get';

	/**
	 * @var int
	 */
	private $writable = 0;


	/**
	 *
	 * @param ReflectionClass $object
	 * @param string $property property name
	 * @param string $canonical property name
	 * @param bool $readable 読み込み可能かどうか
	 * @param string $readPrefix 読み込みメソッドのプレフィックス is/get
	 * @param bool $writable
	 */
	public function __construct(ReflectionClass $reflectionClass, $property, $canonical, $readable, $readPrefix, $writable)
	{
		$this->reflectionClass = $reflectionClass;
		$this->canonical = $canonical;
		$this->property = $property;
		$this->readable = $readable;
		$this->readPrefix = $readPrefix;
		$this->writable =$writable;
	}

	/**
	 * プロパティー名を返す
	 * @return string
	 */
	public function getName()
	{
		return $this->property;
	}

	/**
	 * プロパティーの型を返す、このメソッドアノテーションで指定されている型を返します。
	 *
	 * @param $target
	 * @return string "int", "string", "object"
	 */
	public function getType()
	{
		// TODO : アノテーション
		return null;
//		$method = $this->getReadMethod();
//		$value = $method->invoke($target);
//		return ($value !== null) ? gettype(gettype) : null;
	}

	/**
	 * プロパティーのクラス型を返します
	 * @return string クラス名、クラスがわからない場合は、<code>null</code>を返します
	 */
	public function getClassType()
	{
		if ($this->writable === self::ACCSESSOR) {
			$method = $this->reflectionClass->getMethod('set'.$this->canonical);
			$parameters = $method->getParameters();
			$paramClass = $parameters[0]->getClass();
			return ($paramClass !== null ? $paramClass->getName() : null);
		} else if ($this->writable === self::PROPERTY) {
			// TODO : アノテーション
		}

		return null;
	}

	/**
	 * プロパティーが読み込み可能かどうかを返す
	 * @return bool
	 */
	public function isReadable()
	{
		return $this->readable > 0;
	}

	/**
	 * プロパティーが書き込み可能かどうかを返す、このメソッドは有効な書き込みメソッドが存在
	 * するかどうかを返します。ただしプロパティーがオブジェクト型の参照を持つ場合、読み込み
	 * プロパティーを使いプロパティーを変更することができることに注意してください
	 *
	 * @return bool
	 */
	public function isWritable()
	{
		return $this->writable > 0;
	}

	/**
	 * プロパティーの書き込みオブジェクトを返す
	 * @return PropertyWriter
	 */
	public function getWriter()
	{
		if ($this->writable === self::ACCSESSOR) {
			$method = $this->reflectionClass->getMethod('set'.$this->canonical);
			return new AccessorPropertyWriter($method);
		} else if ($this->writable === self::PROPERTY) {
			$property = $this->reflectionClass->getProperty($this->canonical);
			return new DirectPropertyReadWriter($property);
		} else {
			return null;
		}
	}

	/**
	 * プロパティーの読み込みメソッドを返す
	 * @return PropertyReader
	 */
	public function getReader()
	{
		if ($this->readable === self::ACCSESSOR) {
			$method = $this->reflectionClass->getMethod($this->readPrefix . ucfirst($this->canonical));
			return new AccessorPropertyReader($method);
		} else if ($this->readable === self::PROPERTY) {
			$property = $this->reflectionClass->getProperty($this->canonical);
			return new DirectPropertyReadWriter($property);
		} else {
			return null;
		}
	}
}


/**
 * ネーティブな配列のラッパーとして動作するリストクラス
 *
 * <pre>
 * $list = new ArrayList();
 * echo count($list);
 * $list->add(new stdClass());
 * $list->addAll(new ArrayList());
 * </Pre>
 *
 *
 * @version 1.1 2009/03/05
 * @author Kenji Namba
 * @see IteratorAggregate ネーティブ配列インターフェース
 * @see Collection
 * @see Countable {@link #count}関数のサポート
 * @since Barge 1.0 2008/09/11
 */
class ArrayList implements ArrayAccess, IteratorAggregate, Collection, Countable
{
	const SORT_ASC = SORT_ASC;
	const SORT_DESC = SORT_DESC;
	const SORT_STRING = SORT_STRING;
	const SORT_NUMERIC = SORT_NUMERIC;

	/**
	 * @var array
	 * @since 1.0 2006/09/11
	 */
	protected $items = array();

	/**
	 * コンストラクタ
	 *
	 * <pre>
	 * new ArrayList();
	 * new ArrayList(1, 2, 3);
	 * new ArrayList(array(1, 2, 3));
	 * new ArrayList(new ArrayList());
	 * </pre>
	 *
	 * @param mixed $array arrayまたは<code>ArrayList</code>
	 * @param mixed ...
	 * @since 1.0 2006/09/11
	 */
	public function __construct($array = null /*, ... */)
	{
		$args = func_get_args();
		if (count($args) === 1 && is_array($array)) {
			$this->items = array_values($array);
		} else if (count($args) === 1 && $array instanceof ArrayList) {
			$this->items = $array->toArray();
		} else {
			$this->items = $args;
		}
	}

	/**
	 * @return Iterator
	 * @since 1.0 2006/09/11
	 * @see IteratorAggregate#getIterator
	 */
	public function getIterator()
	{
		return new ArrayIterator($this->items);
	}

	/**
	 * インデックスの最後に値を追加する
	 *
	 * @param mixed $value
	 * @return ArrayList 自分自身のインスタンス
	 * @since 1.0 2006/09/11
	 */
	public function add($value)
	{
		$this->items[] = $value;
		return $this;
	}

	/**
	 * リストの配列(Array)表現を返す
	 *
	 * @return array
	 * @since 1.0 2006/09/11
	 * @see Collection#toArray
	 */
	public function toArray()
	{
		return $this->items;
	}

	/**
	 * リストが0個の要素を持つかどうかを返す
	 *
	 * @return bool
	 * @since 1.0 2006/09/11
	 * @see Collection#isEmpty
	 */
	public function isEmpty()
	{
		return (count($this->items) == 0);
	}

	/**
	 * リストが１個以上の要素を持つかどうかを返す
	 *
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public function hasElement()
	{
		return (count($this->items) > 0);
	}

	/**
	 * リストの最初の要素を返します。要素がない場合は<code>null</code>が返されます。
	 *
	 * @return mixed
	 * @since 1.2 2006/09/11
	 */
	public function firstElement()
	{
		if ($this->hasElement()) {
			return $this->items[0];
		} else {
			return null;
		}
	}

	/**
	 * リストを連結します
	 *
	 * @param string $delimiter
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public function join($delimiter)
	{
		$buff = null;
		$i = 0;
		foreach ($this->items as $item) {
			$buff.=(($i++ > 0 ? $delimiter : '').$item);
		}
		return $buff;
		// implode?
	}

	/**
	 * 指定した要素の値を取得する
	 *
	 * {@return mixed}
	 * @since 1.0 2006/09/11
	 * @see Collection#get
	 * @exception IndexOutOfBoundsException 指定された要素番号が配列の範囲外の場合にスロ
	 * 			ーされます
	 */
	public function get($offset)
	{
		if ($offset < 0 || $offset > (count($this->items) - 1)) {
			$message = "Specified index '$offset' is outside range.";
			throw new IndexOutOfBoundsException($message);
		}
		return $this->items[$offset];
	}

	/**
	 * 指定した要素に値を設定する
	 *
	 * @param int $offset 値を設定する要素番号、0から開始します
	 * @param mixed $value 設定する値
	 * @since 1.0 2006/09/11
	 * @see Collection#set
	 * @exception IndexOutOfBoundsException 指定された要素番号が配列の範囲外の場合にスロ
	 * 			ーされます
	 */
	public function set($offset, $value)
	{
// FIXME : $array[] = 1; // $offset に空が渡される
		if (!is_int($offset) || $offset < 0 || ($offset > (count($this->items) - 1))) {
			$message = "Specified index '{$offset}' is outside range.";
			throw new IndexOutOfBoundsException($message);
		}
		$this->items[$offset] = $value;
	}

	/**
	 * Returns count of elements.
	 *
	 * @return int
	 * @since 1.0 2006/09/11
	 * @see Collection#size
	 */
	public function size()
	{
		return count($this->items);
	}

	/**
	 * 現在の配列の末尾に配列を追加します
	 *
	 * <pre>
	 * $list1 = new ArrayList();
	 * $list2 = new ArrayList(1, 2);
	 * $list1->addAll($list2);
	 * $list1->addAll(new ArrayList(1, 2, 3));
	 * </pre>
	 *
	 * @param ArrayList|array $array
	 * @return ArrayList 自分自身のインスタンス
	 * @since 1.0 2006/09/11
	 * @throws IllegalArgumentException 自分自身のインスタンスを再帰的に指定された
	 * 				場合にスローされる
	 */
	public function addAll($array)
	{
		if ($array instanceof ArrayList) {
			$array = $array->toArray();
		} else if (is_array($array)) {
		} else {
			$message = "Argument type must be array or ArrayList.";
			throw new IllegalArgumentException($message);
		}
//		$array = (($array instanceof ArrayList) ? $array->toArray() : $array);
		if ($this === $array) {
			$message = "Argument is Circulation.";
			throw new IllegalArgumentException($message);
		}
		$this->items = array_merge($this->items, $array);
//		foreach ($array as $item)
//			$this->items[] = $item;
		return $this;
	}

	/**
	 * 指定した要素から指定長のリストを複製します
	 *
	 * @param int $offset オフセットを指定します。オフセットは0から開始します。また負の
	 * 					インデックスを指定した場合、リストの末尾からのインデックスとなります。
	 * @param int $length[optional] = null 新しく作成するリストのオフセットからの長さ
	 * 					を指定します。負の数を指定することもできます。
	 * @return ArrayList 新しいリスト
	 * @since 1.0 2006/09/11
	 */
	public function subSequence($offset, $length = null)
	{
		return new ArrayList(array_slice($this->items, $offset, $length));
	}

	/**
	 * 要素の個数を返します
	 *
	 * @return int
	 * @see Countable#count
	 * @since 2008/07/26
	 */
	public function count()
	{
		return count($this->items);
	}

	/**
	 * @param mixed $offset
	 * @param mixed $value
	 * @see ArrayAccess#offsetSet()
	 */
	public function offsetSet($offset, $value)
	{
		$this->set($offset, $value);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetUnset
	 * @throws IllegalAccessException unsetを呼び出すことはできません
	 */
	public function offsetUnset($offset)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetExists
	 */
	public function offsetExists($offset)
	{
		if ($offset < 0 || ($offset > (count($this->items) - 1))) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetGet
	 */
	public function offsetGet($offset)
	{
		return $this->get($offset);
	}

	/**
	 * @param int $offset
	 * @return mixed 削除したオブジェクト
	 * @throws IndexOutOfBoundsException 指定されたオフセットが不正
	 * @since 1.1 2009/03/05
	 */
	public function remove($offset)
	{
		if (!is_int($offset) || $offset < 0 || $offset > (count($this->items) - 1)) {
			$message = "Specified index '$offset' is outside range.";
			throw new IndexOutOfBoundsException($message);
		}

		$value = $this->items[$offset];
		$upper = count($this->items) - 1;
		for ($i = $offset + 1; $i <= $upper; $i++) {
			$this->items[$i - 1] = $this->items[$i];
		}
		unset($this->items[$upper]);
		return $value;
	}

	/**
	 * @since 1.3.1 2009/04/12
	 * @return string
	 */
	public function __toString()
	{
		return get_class($this);
	}

	/**
	 * リストをソートします、このメソッドはソート済みのリストを返します
	 * <pre>
	 * $list = new AraryList();
	 * $list->sort(new ComparatorImpl());
	 * $list->sort(function($a, $b) { return strcmp($a, $b); } );
	 * </pre>
	 * @param Closure|Comparator $comparator {@link Comparator}の実装クラスまたは
	 * 			クロージャ
	 * @return ArrayList
	 */
	public function sort($comparator = null)
	{
		if (!($comparator instanceof Closure) &&
			!($comparator instanceof Comparator)) {
			$message = String::format();
			throw new IllegalArgumentException($message);
		}
		$list = $this->items;
		usort($list, ($comparator instanceof Comparator ? array($comparator, 'compare') : $comparator));
		return new ArrayList($list);
	}

	/**
	 * リスト内の重複する項目をユニーク化します
	 *
	 * @param ArrayList
	 */
	public function unique()
	{
		return new ArrayList(array_unique($this->items));
	}

	/**
	 * 指定された値が最初に現れるオフセットを返します、値が見つからない場合は、
	 * <code>-1</code>を返します。
	 *
	 * @param mixed $value
	 * @return int
	 */
	public function indexOf($value)
	{
		$ret = array_search($value, $this->items, true);
		return $ret !== false ? $ret : -1;
	}
}

/**
 * @version 1.0
 * @author Kenji Namba
 */
interface Comparator
{
	/**
	 * @param $a
	 * @param $b
	 * @return int
	 */
	public function compare($a, $b);
}

/**
 * Map object (named array wrapper).
 *
 * @version 1.0
 * @author Kenji Namba
 * @see IteratorAggregate - Native interface.
 * @see Collection
 * @since Barge 1.1 2006/09/11
 */
class HashMap implements IteratorAggregate, Collection, ArrayAccess, Countable
{
	/**
	 * @var array
	 * @since 1.0 2006/09/11
	 */
	protected $items = array ();

	/**
	 * A constractor.
	 *
	 * @param array|Collection $map
	 * @since 1.0 2006/09/12
	 * @throws IllegalArgumentException 配列またはコレクション以外が指定された場合スローされる
	 */
	public function __construct($map = null)
	{
		if ($map == null) {
		} elseif (is_array($map)) {
			$this->items = $map;
		} elseif ($map instanceof Collection) {
			$this->items = $map->toArray();
		} else {
			$message = "HashMap : 不正なパラメータ '{$map}' が指定されました。";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * Required definition of interface IteratorAggregate
	 *
	 * @since 1.0 2006/09/11
	 * @return ArrayIterator
	 * @see IteratorAggregate#getIterator
	 */
	public function getIterator()
	{
		return new ArrayIterator($this->items);
	}

	/**
	 * Apend object at last index.
	 *
	 * @param mixed $key
	 * @param mixed value
	 * @return HashMap 自分自身のインスタンス
	 * @since 1.0 2006/09/11
	 */
	public function put($key, $value)
	{
		$this->items[(string)$key] = $value;
		return $this;
	}

	/**
	 * Return array is empty.
	 *
	 * @return boolean
	 * @since 1.0 2006/09/11
	 * @see Collection#isEmpty
	 */
	public function isEmpty()
	{
		return (count($this->items) === 0);
	}

	/**
	 * Return native array.
	 *
	 * @return array
	 * @since 1.0 2006/09/11
	 * @see Collection#toArray
	 */
	public function toArray()
	{
		return $this->items;
	}

	/**
	 * @param mixed $key
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public function contains($key)
	{
		return isset($this->items[(string)$key]);
	}

	/**
	 * @param mixed $key
	 * {@return mixed}
	 * @since 1.0 2006/09/11
	 */
	public function get($key)
	{
		$key = (string)$key;
		if (isset($this->items[$key])) {
			return $this->items[$key];
		} else {
			return null;
		}
	}

	/**
	 * Return count of collection.
	 *
	 * @return bool
	 * @since 1.0 2006/09/11
	 * @see Collection#size
	 */
	public function size()
	{
		return count($this->items);
	}

	/**
	 * キーのリストを返します
	 *
	 * @return ArrayList ハッシュのキー値のみのリスト
	 * @since 1.0 2006/09/11
	 */
	public function keys()
	{
		return new ArrayList(array_keys($this->items));
	}

	/**
	 * 値のリストを返します
	 *
	 * @return ArrayList
	 * @since 1.0 2006/09/11
	 */
	public function values()
	{
		return new ArrayList(array_values($this->items));
	}

	/**
	 * @param mixed $offset
	 * @param mixed $value
	 * @see ArrayAccess#offsetSet()
	 */
	public function offsetSet($offset, $value)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetUnset()
	 */
	public function offsetUnset($offset)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetExists()
	 */
	public function offsetExists($offset)
	{
		return $this->contains($offset);
	}

	/**
	 * @param mixed $offset
	 * @see ArrayAccess#offsetGet()
	 */
	public function offsetGet($offset)
	{
		return $this->get($offset);
	}

	/**
	 * @param Collection|array $array
	 * @return HashMap 自分自身のインスタンス
	 * @since 1.0 2006/09/11
	 */
	public function addAll($array)
	{
		$array = ($array instanceof Collection ? $array->toArray() : $array);
		if ($this === $array) {
			$message = "Argument is Circulation.";
			throw new IllegalArgumentException($message);
		}

		$this->items = array_merge($this->items, $array);
		return $this;
	}

	/**
	 * 要素の個数を返します
	 *
	 * @return int
	 * @see Countable#count
	 * @since 2008/07/26
	 */
	public function count()
	{
		return count($this->items);
	}

	/**
	 * @since 1.3.1 2009/04/12
	 * @return string
	 */
	public function __toString()
	{
		return get_class($this);
	}
}

/**
 * キーと値のペア
 *
 * @see Bean
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
class Entry extends Bean
{
	/**
	 * @var mixed
	 */
	protected $key;

	/**
	 * @var mixed
	 */
	protected $value;

	public function __construct($key, $value)
	{
		$this->key = $key;
		$this->value = $value;
	}

	/**
	 * @return mixed
	 */
	public function getKey()
	{
		return $this->key;
	}

	/**
	 * @return mixed
	 */
	public function getValue()
	{
		return $this->value;
	}
}

/**
 *
 * @version 1.0 2008/09/18
 * @since Barge 1.1 2008/09/18
 */
class Codec
{
	/**
	 * MD5
	 *
	 * @param string $value
	 * @return string
	 */
	public static function md5($value)
	{
		return md5($value);
	}

	/**
	 * @param unknown_type $value
	 * @return unknown
	 */
	public static function encodeBase64($value)
	{
		return base64_encode($value);
	}

	/**
	 * @param unknown_type $value
	 * @return unknown
	 */
	public static function decodeBase64($value, $strict = null)
	{
		return base64_decode($value, $strict);
	}

	/**
	 * @param string $value
	 * @param $salt
	 * @return string
	 */
	public static function crypt($value, $salt = null)
	{
		// FIXME :
		//mcrypt_list_algorithms();
		return crypt($value, $salt);
	}

	/**
	 * SHA-1ハッシュの生成
	 * @param string $value
	 * @param bool $rowOutput
	 * @return string
	 */
	public static function sha1($value, $rowOutput = null)
	{
		return sha1($value, $rowOutput);
	}

}

/**
 *
 * @version 1.0 2008/09/18
 * @since Barge 1.1 2008/09/18
 */
class URLUtils
{
	/**
	 * @param string $paramString
	 * @return HashMap
	 */
	public static function parseQueryString($paramString)
	{
		$map = new HashMap();
		$fields = explode('&', $paramString);
		foreach ($fields as $field) {
			$values = explode('=', $field);
			if (count($values) == 2)
			$map->put($values[0], urldecode($values[1]));
		}
		return $map;
	}

	/**
	 * クエリーパラメータ形式の文字列を作成する
	 * @param string $paramString
	 * @return HashMap
	 */
	public static function toQueryString(HashMap $params)
	{
		$query = null;
		foreach ($params as $key => $name) {
			$query .= ($query != null ? '&' : '') .
			$key .
			'=' .
			urlencode($name);
		}
		return $query;
	}

	/**
	 * @param string $value
	 * @return string
	 */
	public static function encodeURL($value)
	{
		return urlencode($value);
	}

	/**
	 * @param string $value
	 * @return string
	 */
	public static function decodeURL($value)
	{
		return urldecode($value);
	}
}

///**
// * @see HashMap
// * @since Barge 1.1 2006/10/18
// */
//class QueryMap extends HashMap
//{
//	/**
//	 * @param [string query_string = null]
//	 * @since 1.0 2006/10/18
//	 */
//	public function __construct($query_string = null)
//	{
//		$query_map = new HashMap();
//		if ($query_string != null) {
//			$field_arr = String::split('&', $query_string);
//			foreach ($field_arr as $field) {
//				$key_value_arr = String::split('=', $field);
//				if ($key_value_arr->size() == 2)
//				$query_map->put($key_value_arr->get(0), urldecode($key_value_arr->get(1)));
//			}
//		}
//		parent::__construct($query_map);
//	}
//
//	/**
//	 * @return string
//	 * @since 1.0 2006/10/21
//	 */
//	public function getQueryString()
//	{
//		$query_string = null;
//		foreach ($this->items as $key => $name) {
//			$query_string .= ($query_string != null ? '&' : '') .
//			$key .
//			'=' .
//			urlencode($name);
//		}
//		return $query_string;
//	}
//
//	/**
//	 * @return string
//	 * @since 1.0 2006/10/21
//	 */
//	public function __toString()
//	{
//		return $this->getQueryString();
//	}
//}



///**
// * 文字列ユーティリティー
// *
// * @author Kenji Namba
// * @version 1.0
// * @since Barge 1.0 2006/08/12
// */
//class String
//{
//	private $string;
//
//	public function __construct($str)
//	{
//		$this->string = strval($str);
//	}
//
//	public function __toString()
//	{
//		return $this->string;
//	}
//
//	public function concat($str)
//	{
//		return new String($this->string.strval($str));
//	}
//
//	public function substr($value, $offset, $length = null)
//	{
//		$ret = ($length === null ? mb_substr($value, $offset)
//		: mb_substr($value, $offset, $length));
//		return new String(($ret === false ? '' : $ret));
//	}
//}
//
//function s($str)
//{
//	return new String($str);
//}

/**
 * 文字列バッファー
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class StringBuffer
{
	/**
	 * @var string
	 * @since 1.0 2006/08/12
	 */
	private $buffer;

	/**
	 * コンストラクタ
	 * @param string $value
	 */
	public function __construct($value = null)
	{
		$this->buffer = (string)$value;
	}

	/**
	 * @param string $value
	 * @return StringBuffer 自分自身のインスタンス
	 */
	public function append($value)
	{
		$this->buffer.=$value;
		return $this;
	}

	/**
	 * @param string $value
	 * @return StringBuffer 自分自身のインスタンス
	 */
	public function appendln($value = '')
	{
		$this->buffer.=$value."\n";
		return $this;
	}

	/**
	 * @return StringBuffer 自分自身のインスタンス
	 */
	public function clear()
	{
		$this->buffer = null;
		return $this;
	}

	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->buffer;
	}

	/**
	 * @return string
	 */
	public function toString()
	{
		return $this->buffer;
	}
}


/**
 * 文字列ユーティリティー
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class String
{
	/**
	 * @param string $value
	 * @return bool
	 * @since 1.0 2006/08/12
	 */
	public static function isEmpty($value)
	{
		return ($value == null || strval($value) === '');
	}

	/**
	 * @param string $value
	 * @return bool
	 * @since 1.0 2006/08/12
	 */
	public static function isBlank($value)
	{
		return LocaleString::getInstance()->blanks($value);
//		return ($value == null || trim(strval($value)) === '');
	}

	/**
	 * 指定された文字列が空かどうかを判定します
	 *
	 * @param string $value
	 * @return bool
	 * @since 1.0 2006/08/12
	 */
	public static function isNotEmpty($value)
	{
		return !self::isEmpty($value);
	}

	/**
	 * @param string $value
	 * @return bool
	 * @since 1.0 2006/08/12
	 */
	public static function isNotBlank($value)
	{
		return !self::isBlank($value);
	}

	/**
	 * @param string $string1
	 * @param string $string2
	 * @return bool
	 * @since 1.0 2006/08/12
	 */
	public static function compare($string1, $string2)
	{
		return (strcmp((string)$string1, (string)$string2));
	}

	/**
	 * @param string $value
	 * @param string $default = ''
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function defaultString($value, $default = '')
	{
		return ($value === null ? $default : $value);
	}

	/**
	 * 文字列先頭および最後から空白文字を取り除く。デフォルトでは、以下の文字が対象
	 * となります。
	 *
	 * " " 通常の空白
	 * "\t" タブ
	 * "\n" リターン
	 * "\r" 改行
	 * "\0" NULバイト
	 * "\x0B" 垂直
	 *
	 * @param string $value
	 * @param string $charlist
	 * @return string
	 * @since 1.2 2009/01/24
	 */
	public static function trim($value, $charlist = null)
	{
		if ($value === null) return null;
		return ($charlist !== null ? trim($value, $charlist) : trim($value));
	}

	/**
	 * @param string $value
	 * @param string $default = ''
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function defaultIfEmpty($value, $default = '')
	{
		return (self::isEmpty($value) ? $default : $value);
	}

	/**
	 * 文字列を分割します。このメソッドは、分割した要素を<code>ArrayList</code>	を返します
	 *
	 * @param string $target 対象文字列に<code>null</code>が指定された場合は、空のリストを返します。
	 * 						空文字が指定された場合、空文字を含む要素１のリストを返します。
	 * @param string $delimiter
	 * @return ArrayList
	 * @since 1.0 2006/09/11
	 */
	public static function split($target, $delimiter)
	{
		// @since 1.2.2 --
//		if ($target === null) return new ArrayList();
//		return new ArrayList(split($pattern, (string)$target));
		return self::explode($target, $delimiter);
	}

	/**
	 *
	 * @param string $target 対象文字列に<code>null</code>が指定された場合は、空のリストを返します。
	 * 						空文字が指定された場合、空文字を含む要素１のリストを返します。
	 * @param string $delimiter 分割する文字
	 * @return ArrayList
	 * @since 1.2 2009/02/07
	 */
	public static function explode($target, $delimiter)
	{
		if ($target === null) return new ArrayList();
		return new ArrayList(explode($delimiter, (string)$target));
	}

	/**
	 * 文字列を指定オフセットから指定文字数分切り出します。文字数が指定されない場合は指定オフ
	 * セットから末尾までが対象になります。このメソッドは、マルチバイト文字列を１文字として扱います。
	 *
	 * @param string $value 対象文字列
	 * @param int $offset 開始オフセット
	 * @param int $length 開始オフセットからの長さ、文字列長以降を指定しても例外は発生しません
	 * 				負の値が指定された場合は、文字列の長さからの逆オフセットとなります。
	 * @return string 切り出した文字列。通常<code>substr</code>は、オフセットが不正の
	 * 				場合<code>false</code>を返しますが、このメソッドは空文字を返します。また入力文字
	 * 				が<code>null</code>または空文字の場合、入力文字をそのまま返します。
	 */
	public static function substr($value, $offset, $length = null)
	{
		if (empty($value))
		return $value;

		$ret = ($length === null ? mb_substr($value, $offset)
		: mb_substr($value, $offset, $length));
		return ($ret === false ? '' : $ret);
	}

	/**
	 * Java の substring のエミュレーション
	 * @param string $value
	 * @param int $start 開始オフセット
	 * @param int $end 終了オフセット、実際の終了位置でなく終了位置 + 1 を指定します。
	 * 			{@link String::substr()}と異なり、負の値を指定することはできません。
	 * @return string
	 * @throws IllegalArgumentException 終了位置に負のオフセットが指定された場合
	 */
	public static function substring($target, $start, $end = null)
	{
		if (empty($target))
			return $target;
		if ($end === null) $end = mb_strlen($target);
		if ($end < 0) {
			$message = '終了位置に負のオフセットを指定することはできません。';
			throw new IllegalArgumentException($message);
		}
		return mb_substr($target, $start, $end - $start);
	}

	/**
	 *
	 * @param string $value 検索対象
	 * @param string $start 検索文字列
	 * @param int $start 任意で検索開始オフセットを指定します。負のオフセットが指定された
	 *        場合は、文字列の末尾からのオフセット指定になります。省略された場合最初
	 *        のオフセットから検索が開始されます
	 * @return int 検索文字が現れる最初のオフセットを返します。検索文字が見つからない場合は、<code>-1
	 *        </code>を返します。
	 */
	public static function indexOf($value, $search, $start = null)
	{
		if ($start === null)
			$ret = mb_strpos($value, $search);
		else
			$ret = mb_strpos($value, $search, $start);
		return ($ret === false ? -1 : $ret);
	}

	/**
	 * @param string $value
	 * @param int $offset
	 * @return string
	 */
	public static function charAt($value, $offset)
	{
		if ($offset >= 0 && self::length($value) > $offset) {
			return self::substr($value, $offset, 1);
		} else {
			$message = "Specified index '$offset' is outside range.";
			throw new IndexOutOfBoundsException($message);
		}
	}

	/**
	 * @param string $value
	 * @param int $pos
	 * @pamra string $default
	 * @return string
	 */
	public static function defaultCharAt($value, $pos, $default = null)
	{
		if (mb_strlen($value) > $pos) {
			return $value[$pos];
		} else {
			return $default;
		}
	}

	/**
	 * @param string $value
	 * @param string $search
	 * @param int $start[optional]
	 * @return int 検索文字が現れる最初のオフセットを返します。検索文字が見つからない場合は、
	 * 		<code>-1</code>を返します。
	 */
	public static function lastIndexOf($value, $search, $start = null)
	{
		$ret = strripos($value, $search, $start);
		return ($ret === false ? -1 : $ret);
	}

	/**
	 *
	 * @param string $string
	 * @param array|string $find
	 * @param array|string $replace
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function replace($string, $find, $replace)
	{
		if ($string === null) return null;
		return str_replace($find, $replace, $string);
	}

	/**
	 *
	 * @param string $string
	 * @param array|string $find
	 * @param array|string $replace
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function remove($string, $find)
	{
		if ($string === null) return null;
		return str_replace($find, $replace, '');
	}

	/**
	 * @param string $string
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function upperCase($string)
	{
		if ($string === null) return $string;
		return strtoupper($string);
	}

	/**
	 * @param string $string
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function lowerCase($string)
	{
		if ($string === null) return $string;
		return strtolower($string);
	}

	/**
	 * @param string $string
	 * @param string $with
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public static function startsWith($string, $with)
	{
		return (substr($string, 0, strlen($with)) === (string)$with);
	}

	const PAD_BYTE = 1;
	const PAD_LENGTH = 2;
	const PAD_WIDTH = 3;

	/**
	 * 指定された文字列を指定された文字幅で左詰めします。
	 *
	 * @param string $string 対象文字列
	 * @param int $len 文字数、すべての１文字を１とカウントします
	 * @param string $char 埋める文字
	 * @return bool
	 * @since 1.0 2006/09/11
	 * @throws IllegalArgumentException パラーメータ不正
	 */
	public static function leftPad($string, $len, $char = ' ', $type = self::PAD_LENGTH)
	{
		if (self::isEmpty($char)) {
			$message = '無効な文字が指定されました。';
			throw new IllegalArgumentException($message);
		}
		$string = strval($string);
		if ($type === self::PAD_LENGTH) {
			while (mb_strlen($string) < $len)
			$string = $char.$string;
		} else if ($type === self::PAD_WIDTH) {
			while (self::width($string) < $len)
			$string = $char.$string;
		} else if ($type === self::PAD_BYTE) {
			while (strlen($string) < $len)
			$string = $char.$string;
		} else {
			// TODO :
		}
		return $string;
	}

	/**
	 * @param string $string
	 * @param string $len 文字数、すべての１文字を１とカウントします
	 * @param string $char
	 * @return bool
	 * @since 1.0 2006/09/11
	 * @throws IllegalArgumentException パラーメータ不正
	 */
	public static function rightPad($string, $len, $char = ' ', $type = self::PAD_LENGTH)
	{
		if (self::isEmpty($char)) {
			$message = '無効なパッディング文字が指定されました。';
			throw new IllegalArgumentException($message);
		}
		$string = strval($string);
		if ($type === self::PAD_LENGTH) {
			while (mb_strlen($string)<$len)
				$string = $string.$char;
		} else if ($type === self::PAD_WIDTH) {
			while (self::width($string) < $len)
				$string = $string.$char;
		} else if ($type === self::PAD_BYTE) {
			while (strlen($string) < $len)
				$string = $string.$char;
		} else {
			// TODO :
		}
		return $string;
	}

	/**
	 * 指定された文字列で対象文字列が終るかどうかを返す
	 *
	 * @param string $string
	 * @param string $with
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public static function endsWith($string, $with)
	{
		return (substr($string, -1 * strlen($with)) === $with);
	}

	/**
	 * <pre>
	 * String::format('%d', 1);
	 * String::format('%d=%d', 1, 2);
	 * String::format('%s=%d', array('key', 1));
	 * </pre>
	 * @param string $format フォーマット文字列
	 * @param mixed $arg0
	 * @param ...
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function sprintf($format, $arg0 = null/*, ... */)
	{
		if (self::isEmpty($format))
			return $format;

		$args = func_get_args();
		if (count($args) === 0) {
			throw Error("Parameter less than 1");
		}
		$expression = $args[0];
		$list = array();
		if (count($args) > 1) {
			if (is_array($args[1])) {
				$list[] = $format;
				$list = array_merge($list, $args[1]);
			} else {
				$list = $args;
			}
		}
		$reflection = new ReflectionFunction('sprintf');
		return $reflection->invokeArgs($list);
	}

	/**
	 * 文字列を連結する
	 *
	 * @param ... スカラー値
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function concat(/*...*/)
	{
		$args = func_get_args();
		$buff = null;
		foreach ($args as $value) {
			$buff.=$value;
		}
		return $buff;
	}

	/**
	 * Perl正規表現より文字列がパターンに一致するかどうかを返す
	 *
	 * <pre>
	 * String::match('1234567890', '/\w+/')
	 * </pre>
	 *
	 * @param string $string
	 * @param string $pattern Perl互換の正規表現パターン
	 * @return bool
	 */
	public static function match($string, $pattern)
	{
		return (preg_match($pattern, $string) > 0);
	}

	/**
	 * 文字列を指定回数だけ繰り返した文字列を作成する
	 *
	 * @param string $source
	 * @param int $count
	 * @return string
	 */
	public static function repeat($source, $count)
	{
		return str_repeat($source, $count);
	}

	/**
	 * 文字列の先頭を大文字（Upper）に変換する
	 *
	 * @param string $string
	 * @return string
	 */
	public static function capitalize($string)
	{
		if (empty($string)) return $string;
		return strtoupper(substr($string, 0, 1)) . (strlen($string) > 1 ? substr($string, 1) : '');
	}

	/**
	 * 文字列をラクダ文字に変換する
	 *
	 * <pre>
	 * ""    => ""
	 * "a-b" => "AB"
	 * "abc-def" => "AbcDef"
	 * "abc_def" => "AbcDef"
	 * "abc def" => "AbcDef"
	 * </pre>
	 *
	 * @param string $string
	 * @param bool $lcfirst
	 * @return string
	 */
	public static function camelize($string, $lcfirst = false)
	{
		if (empty($string)) return $string;
		$string = preg_replace("/([_-\s]?([a-z0-9]+))/e", "ucwords('\\2')", $string);
		if ($lcfirst) {
			$string = strtolower(substr($string, 0, 1)) . substr($string, 1);
		}
		return $string;
	}

	/**
	 * 文字数を返す
	 *
	 * @param string $string
	 * @param int 文字数
	 * @return string
	 */
	public static function length($string)
	{
		return mb_strlen($string);
	}

	/**
	 * @param string $text
	 * @return Iterator
	 */
	public static function getIteraor($text)
	{
		return new CharactorIterator($text);
	}

	/**
	 * 文字幅を返す
	 *
	 * @param string $string
	 * @param int 文字数
	 * @return string
	 * @see mb_strlen
	 */
	public static function width($string)
	{
		return LocaleString::getInstance()->width($string);
	}

	/**
	 * バイト数を返す
	 *
	 * @param string $string
	 * @param int 文字数
	 * @return string
	 * @see mb_strlen
	 */
	public static function byte($string)
	{
		return strlen($string);
	}

	const ESCAPE_NONE = 0;
	const ESCAPE_YEN = 1;
	const ESCAPE_REPEAT = 2;

	/**
	 * 文字列をクオートで囲む
	 *
	 * @param string $string 対象文字列
	 * @param string $quoter クオート文字
	 * @param int $escape エスケープのタイプ
	 * @return unknown
	 */
	public static function quote($string, $quoter = "'", $escape = self::ESCAPE_NONE)
	{
		if ($escape === self::ESCAPE_YEN) {
			$string = self::replace($string, $quoter, "\\".$quoter);
		} else if ($escape === self::ESCAPE_REPEAT) {
			$string = self::replace($string, $quoter, $quoter.$quoter);
		}
		return ($quoter . $string . $quoter);
	}

	/**
	 * {subcamel:1:2|classlize}
	 * FIXME :	メソッド名がダサイ
	 * <pre>
	 * ABCD - ABCD
	 * AbcDef - Abc Def
	 * Abc Def - Abc Def
	 * abc_def - abc def
	 * </pre>
	 *
	 * @param string $string
	 * @return string
	 */
	public static function subwords($string, $start = 0, $length = null)
	{
		$len = strlen($string);
		$case = -1;
		$list = array();
		$c = 0;
		for ($i = 0; $i < $len; $i++) {
			$ch = $string[$i];
			if ($ch >= 'A' && $ch <= 'Z') {
				if ($case === 2) $c++;
				$list[$c] .= $ch;
				$case = 1;
			} else if ($ch >= 'a' && $ch <= 'z') {
				//				if ($case === 1) $c++;
				$list[$c] .= $ch;
				$case = 2;
			} else if (is_numeric($ch)) {
				$list[$c] .= $ch;
			} else {
				$c++;
			}
		}

		return join('', array_slice($list, $start, $length));;
	}

	/**
	 * 文字列フォーマットに基づきフォーマットした文字を作成する
	 *
	 * <pre>
	 * String::format('id={0}({1})', $id, $path);
	 * String::format('id={id}({path})', array('id'=>$id, 'path'=>$path));
	 * </pre>
	 *
	 * @param $string
	 * @param array|...
	 * @return string
	 * @throws IllegalArgumentException パラメータが不正または、登録されていないフィルータが指定
	 * 		された場合などに発生します
	 * @since 1.0 2006/09/11
	 */
	public static function format($string /*,...*/)
	{
		$args = func_get_args();
		$ret = "";
		$expression = $args[0];
		if ($expression === null)
			return null;

		$expression = strval($expression);
		if (strpos($expression, '{') === false)
			return $expression;

		$list = is_array($args[1]) ? $args[1] : array_splice($args, 1);
		return preg_replace_callback('/{(.*?)}/',
			array(new FormatReplacer($list), 'replace'), $expression);
	}

	/**
	 * ２つの文字列のリーベンシュタイン距離（類似度）を返す
	 *
	 * @param string $str1
	 * @param string $str2
	 * @param int $costIns
	 * @param int $costRep
	 * @param int $costDel
	 * @return flaot
	 */
	public static function levenshtein($str1, $str2, $costIns = null, $costRep = null, $costDel = null)
	{
		return levenshtein($str1, $str2, $costIns, $costRep, $costDel);
	}

	/**
	 * ２つの文字列の類似度をパーセントで返す
	 *
	 * @param string $str1
	 * @param string $str2
	 * @return float
	 * @since 2009/10/10 Suppoted PHP 5.0.0
	 */
	public static function similar($str1, $str2)
	{
		$p = 0.0;
		similar_text($str1, $str2, $p);
		return $p;
	}

}


/**
 * パターン置換コールバック
 *
 * @author Kenji Namba
 * @version 1.0 2008/09/28
 * @since Barge 1.2 2008/09/28
 * @see String#format()
 */
class FormatReplacer
{
	/**
	 * @var array
	 */
	private $list;

	/**
	 * @var string
	 */
	private $handler;

	/**
	 * @var ReflectionClass
	 */
	private $handlerClass;

	/**
	 * コンストラクタ
	 * @param array $list
	 */
	public function __construct(array $list)
	{
		$this->list = $list;
		//$this->handler = 'barge::utils::String';
		//$this->handlerClass = new ReflectionClass($this->handler);
	}

	/**
	 * {@link preg_replace_callback}のためのコールバック
	 * @param string $patterns
	 * @return string
	 * @see preg_replace_callback
	 */
	public function replace($patterns)
	{
		// func:1
		$pipes = explode('|', $patterns[1]);
		$offset = $pipes[0];
		$value = (isset($this->list[$offset])) ? $this->list[$offset] : null;
		$len = count($pipes);
		for ($i = 1; $i < $len; $i++) {
			$params = explode(':', $pipes[$i]);
			$funcargs = array($value);
			$len2 = count($params);
			if ($len2 > 1) {
				for ($j = 1; $j < $len2; $j++) {
					if (preg_match('/^\'(.*)\'$/', $params[$j], $matches)) {
						$funcargs[] = $matches[1];
					} else {
						$funcargs[] = intval($params[$j]);
					}
				}
			}
			if ($params[0] === 'format') {
				$message = "Invalidate modifier 'format' (protection)";
				throw new IllegalArgumentException($message);
			}

			$handler = 'barge\utils\String';
			$reflectioClass = new ReflectionClass($handler);
			if ($reflectioClass->hasMethod($params[0])) {
				/* @var $method ReflectionMethod */
				$method = $reflectioClass->getMethod($params[0]);
				try {
					$value = $method->invokeArgs(null, $funcargs);
				} catch (Exception $e) {
					$message = "Fail to invoke modifier '{$params[0]}', cause {$e->getMessage()}";
					throw new IllegalArgumentException($message);
				}
//			} else if (function_exists($params[0])) {
//				$method = new ReflectionFunction($params[0]);
			} else {
				$message = "Invalidate modifier '{$params[0]}'";
				throw new IllegalArgumentException($message);
			}
//			if (function_exists($params[0])) {
//				$value = call_user_func_array(array('barge::utils::String', $params[0]), $funcargs);
//			}
		}

		return $value;
	}

}

/**
 * @version 1.0 2009/04/25
 * @since barge 1.3 2009/04/25
 */
class CharactorIterator extends ArrayIterator
{
	/**
	 * @param string $text
	 */
	public function __construct($text)
	{
		$len = String::length($text);
		$chars = new ArrayList();
		for ($i = 0; $i < $len; $i++) {
			$chars->add(String::substr($text, $i, 1));
		}
		parent::__construct($chars->toArray());
	}
}


/**
 * @version 1.0 2009/04/25
 * @since barge 1.3 2009/04/25
 */
class ArrayUtils
{
	/**
	 *
	 * @param ArrayList $list
	 * @param [string $glue = ","]
	 * @return string
	 * @since 1.0 2006/09/11
	 */
	public static function join(ArrayList $list, $glue = ',')
	{
		return join($list->toArray(), $glue);
	}

	/**
	 * 指定された配列すべてに共通な値を配列として返す
	 * @param ...<ArrayList|array> ２つ以上のリスト
	 * @return ArrayList
	 */
	public static function intersect($lsit0, $lsit1 /*...*/)
	{
		$list = array();
		foreach (func_get_args() as $arg)
			$list[] = (is_array($arg) ? $arg : $arg->toArray());
		$f = new ReflectionFunction('array_intersect');
		return new ArrayList($f->invokeArgs($list));
	}

	/**
	 * @param ...<ArrayList|array> ２つ以上のリスト
	 * @return ArrayList
	 */
	public static function merge($lsit0, $lsit1 /*...*/)
	{
		$list = array();
		foreach (func_get_args() as $arg)
			$list[] = (is_array($arg) ? $arg : $arg->toArray());
		$f = new ReflectionFunction('array_merge');
		return new ArrayList($f->invokeArgs($list));
	}

//	/**
//	 * 要素中の最初の要素を返す、リストが空だった場合は、<code>null</code>を
//	 * 返します。
//	 *
//	 * @param ArrayList $list
//	 * @return mixed
//	 */
//	public static function firstElement(ArrayList $list)
//	{
//		return $list->isEmpty() ? null : $list->get(0);
//	}
//
//	/**
//	 * 要素中の最後の要素を返す、リストが空だった場合は、<code>null</code>を
//	 * 返します。
//	 *
//	 * @param ArrayList $list
//	 * @return mixed
//	 */
//	public static function lastElement(ArrayList $list)
//	{
//		return $list->isEmpty() ? null : $list->get($list->size() - 1);
//	}
}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
class MapUtils
{
	/**
	 * 配列を再帰的に HashMap に変換します、このメソッドは再帰的に変換を行います
	 *
	 * @param array $array
	 * @return HashMap
	 */
	public static function toMap(array $array)
	{
		$map = new HashMap();
		foreach ($array as $key => $value)
			$map->put($key, (is_array($value) ? self::toMap($value) : $value));
		return $map;
	}

	/**
	 * ２つのマップをそれぞれキー、値として合成し新しいマップを作成します
	 *
	 * @param HashMap $map1
	 * @param HashMap $map2
	 * @return HashMap
	 */
	public static function combine(HashMap $map1, HashMap $map2)
	{
		if ($map1->size() !== $map2->size()) {
			$message = 'サイズが一致しない';
			throw new Exception($message);
		}

		return new HashMap(array_combine($map1->toArray(), $map2->toArray()));
	}
}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
interface TimeValue
{
	/**
	 * @param string $format
	 * @return string
	 * @since 1.0
	 */
	public function format($format);

	/**
	 * @return int
	 * @since 1.0
	 */
	public function getTimeValue();
}

/**
 * コレクションインターフェース
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.1 2006/08/13
 */
interface Collection
{
	/**
	 * @return array
	 * @since 1.0 2006/09/11
	 */
	public function toArray();

	/**
	 * @return bool
	 * @since 1.0 2006/09/11
	 */
	public function isEmpty();

	/**
	 * @return int
	 * @since 1.0 2006/09/11
	 */
	public function size();

}

///**
// * @since 1.2.1 2009/03/20
// */
//class Selector
//{
//	/**
//	 * Enter description here...
//	 *
//	 * @return m
//	 */
//	public static function firstNotNull()
//	{
//		foreach (func_get_args() as $obj) {
//			if ($obj !== null)
//				return $obj;
//		}
//		return null;
//	}
//}

/**
 * @see Bean
 * @see TimeValue
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
class Date extends Bean implements TimeValue
{
	const SUNDAY = 0;
	const MONDAY = 1;
	const FRIDAY = 5;
	const SATDAY = 6;

	const DATE = 10;
	const MONTH = 11;
	const YEAR = 12;

	/**
	 * @var numeric Serialized date
	 * @since 1.0 2006/10/21
	 */
	private $date;

	/**
	 * @param long $date
	 * @since 1.0 2006/10/21
	 */
	public function __construct($date = null)
	{
		if ($date == null)
			$date = System::currentTime()->getTimeValue();
		$this->date = $date;
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getYears()
	{
		return (int)$this->format("Y");
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getMonths()
	{
		return (int)$this->format("m");
	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function getMonth()
	{
		return new Month(clone $this);
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getDates()
	{
		return intval(date('d', $this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function add($field = null, $inc = 1)
	{
		if ($field === null)
		$field = self::DATE;
		switch ($field) {
			case self::DATE:
				$this->date = (mktime(0, 0, 0, $this->getMonths(), $this->getDates() + $inc, $this->getYears()));
				break;
			case self::MONTH:
				$this->date = (mktime(0, 0, 0, $this->getMonths() + $inc, $this->getDates(), $this->getYears()));
				break;
			default:
				$message = "Unported field type";
				throw new IllegalArgumentException($message);
				break;
		}

	}

	/**
	 * 曜日をあらわす定数を返します
	 *
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getWeeks()
	{
		return intval(date('w', $this->date));
	}

	/**
	 * @return string
	 * @since 1.0 2006/10/21
	 */
	public function __toString()
	{
		return strval($this->date);
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getTimeValue()
	{
		return $this->date;
	}

	/**
	 * @return Date
	 * @since 1.0 2006/10/21
	 * @exception IllegalArgumentException 入力が不正
	 */
	public static function parse($val)
	{
		$matchies = array ();
		if (preg_match("/^(\d{4})[^\d](\d{1,2})[^\d](\d{1,2})$/", $val, $matchies)) {
			return new Date(mktime(0, 0, 0, $matchies[2], $matchies[3], $matchies[1]));
		} elseif (preg_match("/^(\d{4})[^\d](\d{2})$/", $val, $matchies)) {
			return new Date(mktime(0, 0, 0, $matchies[2], 1, $matchies[1]));
			//		} elseif (preg_match("/^(\d{4})[^\d](\d{2})[^\d](\d{2})\s(\d{2})[^\d](\d{2})[^\d](\d{2})$/", $val, $matchies)) {
			//			return new Date(mktime(0, 0, 0, $matchies[2], $matchies[3], $matchies[1]));
		} else {
			$message = "Unpersable date pattern '{$val}'";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * @param string $format 日付フォーマット文字列
	 * @since 1.0 2006/10/21
	 */
	public function format($format)
	{
		if (empty($format)) {
			return $format;
		}
		return date($format, $this->date);
	}

	/**
	 * @return int
	 * @since 1.0 2008/06/20
	 */
	public function compareTo(Date $date)
	{
		return strcmp($this->format('Ymd'),
		($date == null ? '00000000' : $date->format('Ymd')));
	}
}


/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
class Timestamp extends Bean implements TimeValue
{
	/**
	 * @var int
	 * @since 1.0 2006/10/21
	 */
	private $date;

	/**
	 * A constractor.
	 *
	 * @param int $date
	 * @since 1.0 2006/10/21
	 */
	public function __construct($date = null)
	{
		if ($date === null)
			$date = System::currentTime()->getTimeValue();
		$this->date = $date;
	}

	/**
	 * @return string
	 * @since 1.0 2006/10/21
	 */
	public function __toString()
	{
		return strval($this->date);
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getTimeValue()
	{
		return $this->date;
	}

	/**
	 * @param $val 文字列
	 * @return Timestamp
	 * @since 1.0 2006/10/21
	 */
	public static function parse($val)
	{
		$matchies = Array();
		if (preg_match("/^(\d{4})[^\d](\d{2})[^\d](\d{2})$/", $val, $matchies)) {
			return new self(mktime(0, 0, 0, $matchies[2], $matchies[3], $matchies[1]));
		} elseif (preg_match("/^(\d{4})[^\d](\d{2})$/", $val, $matchies)) {
			return new self(mktime(0, 0, 0, $matchies[2], 1, $matchies[1]));
		} elseif (preg_match("/^(\d{4})[^\d](\d{2})[^\d](\d{2})\s(\d{2}):(\d{2}):(\d{2})$/", $val, $matchies)) {
			return new self(mktime($matchies[4], $matchies[5], $matchies[6], $matchies[2], $matchies[3], $matchies[1]));
		} else {
			$message = "Unpersable date pattern '{$val}'";
			throw new IllegalArgumentException($message);
		}
	}

	/**
	 * <pre>
	 * $timestamp = new Timestamp();
	 * $timestamp->format('Y-m-d H:i:s');
	 * </pre>
	 *
	 * @param  $format 日付書式、{@link date()}で使用できるものと同じ
	 * @since 1.0 2006/10/21
	 */
	public function format($format)
	{
		return date($format, intval($this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getYears()
	{
		return (int)$this->format("Y");
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getMonths()
	{
		return (int)$this->format("m");
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getDates()
	{
		return intval(date('d', $this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2008/06/20
	 */
	public function getHours()
	{
		return intval(date('H', $this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2008/06/20
	 */
	public function getMinutes()
	{
		return intval(date('i', $this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2008/06/20
	 */
	public function getSeconds()
	{
		return intval(date('s', $this->date));
	}

	/**
	 * @return int
	 * @since 1.0 2008/06/20
	 */
	public function compareTo(Timestamp $datetime)
	{
		return strcmp($this->format('YmdHis'),
		($datetime == null ? '00000000000000' : $datetime->format('YmdHis')));
	}
}


///**
// * @author Kenji Namba
// * @version 1.0
// * @since Barge 1.0 2006/08/14
// */
//class DateUtils
//{
//	/**
//	 * @param Date $date
//	 * @param string $format
//	 * @return string
//	 */
//	public static function format(Date $date, $format)
//	{
//		return strftime($format, $date->getTimeValue());
//	}
//
//	/**
//	 * @return Date
//	 * @since 1.0 2006/10/21
//	 */
//	public static function parse($date_str)
//	{
//		$matchies = array ();
//		if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $val, $matchies)) {
//			return new Date(mktime(0, 0, 0, $matchies[2], $matchies[3], $matchies[1]));
//		} elseif (preg_match("/^(\d{4})-(\d{2})$/", $val, $matchies)) {
//			return new Date(mktime(0, 0, 0, $matchies[2], 1, $matchies[1]));
//		} elseif (preg_match("/^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/", $val, $matchies)) {
//			return new Date(mktime(0, 0, 0, $matchies[2], $matchies[3], $matchies[1]));
//		} else {
//			return null;
//		}
//	}
//}

/**
 * 年月
 *
 * @author Kenji Namba
 * @version 1.0
 * @since barge 1.0 2006/08/14
 * @see barge\utils\TimeValue
 * @see barge\utils\Bean
 */
class Month extends Bean implements TimeValue
{
	/**
	 * @var Date
	 * @since 1.0 2006/10/21
	 */
	private $date;

	/**
	 * @param Date $date
	 * @since 1.0 2006/10/21
	 */
	public function __construct(Date $date = null)
	{
		$this->date = ($date !== null ? $date : new Date());
	}

	/**
	 *
	 * @return ArrayList
	 * @since 1.0 2006/10/21
	 */
	public function getDates()
	{
		$dates = new ArrayList();
		$date = $this->getFirstDate();
		$lastDateOfMonth = $this->getLastDate()->getDay();
		for ($i = 1; $i <= $lastDateOfMonth; $i++)
		$dates->add(clone $date);
		$date->add();	// +1Day
		return $dates;
	}

	/**
	 * @return Date
	 * @since 1.0 2006/10/21
	 */
	public function getLastDate()
	{
		return new Date(mktime(0, 0, 0, $this->date->getMonths() + 1, 0, $this->date->getYears()));
	}

	/**
	 * @return Date
	 * @since 1.0 2006/10/21
	 */
	public function getFirstDate()
	{
		return new Date(mktime(0, 0, 0, $this->date->getMonths(), 1, $this->date->getYears()));
	}

	/**
	 * @param $format 日付フォーマット文字列
	 * @since 1.0 2006/10/21
	 */
	public function format($format)
	{
		return date($format, $this->getTimeValue());
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getTimeValue()
	{
		return mktime(0, 0, 0, $this->date->getMonths(), 0, $this->date->getYears());
	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function getNextMonth()
	{
		$month = $this->date->getMonth();
		$month->addMonth();
		return $month;
	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function getPreviousMonth()
	{
		$month = $this->date->getMonth();
		$month->addMonth(-1);
		return $month;
	}

	/**
	 * @return int
	 * @since 1.0 2006/10/21
	 */
	public function getMonths()
	{
		return intval($this->date->format('m'));
	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function getYears()
	{
		return intval($this->date->format('Y'));
	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function addMonth($inc = 1)
	{
		$this->date->add(Date::MONTH, $inc);
	}

	//	/**
	//	 * @return Month
	//	 * @since 1.0 2006/10/21
	//	 */
	//	public static function parse($string)
	//	{
	//		return new Month();
	//	}

	/**
	 * @return Month
	 * @since 1.0 2006/10/21
	 */
	public function __toString()
	{
		return $this->format('Y-m');
	}
}


/**
 * 数学関数
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/14
 */
final class Math
{
	/**
	 * @param float $value
	 * @return float
	 */
	public static function ceil($value)
	{
		return ceil($value);
	}

	/**
	 * @param mixed $value
	 * @return mixed
	 */
	public static function abs($value)
	{
		return abs($value);
	}

	/**
	 * @param float $value
	 * @return float
	 */
	public static function floor($value)
	{
		return floor($value);
	}

	/**
	 * @param float $value
	 * @param int $value
	 * @return float
	 */
	public static function round($value, $precent = null)
	{
		return round($value, $precent);
	}

	/**
	 * <pre>
	 * Math::min(0.6, -100, 1, 100, 2000);
	 * </pre>
	 *
	 * @param ...
	 * @return float
	 */
	public static function min(/* ... */)
	{
		return min(func_get_args());
	}

	/**
	 * @param ...
	 * @return float
	 */
	public static function max(/* ... */)
	{
		return max(func_get_args());
	}

	/**
	 * 整数型の乱数を作成します
	 *
	 * @param int $min
	 * @param int $max
	 * @return int
	 */
	public static function random($min, $max)
	{
		return rand($min, $max);
	}

	/**
	 * 平方根を返します
	 *
	 * @param int $value
	 * @return float
	 */
	public static function sqrt($value)
	{
		return sqrt($value);
	}

	/**
	 * @param number $base
	 * @param number $exp
	 * @return number
	 */
	public static function pow($base, $exp)
	{
		return pow($base, $exp);
	}
}

/**
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class PerlRegexp
{
	/**
	 * @var string
	 */
	private $pattern;

	/**
	 * @var ArrayList
	 */
	private $_matchies;
	//
	//	/**
	//	 * For match extention (//e).
	//	 *
	//	 * @var ArrayList
	//	 */
	//	private $_runtime_args;

	/**
	 * A constructor.
	 *
	 * @param string $pattern
	 * @since 1.0 2006/11/02
	 */
	public function __construct($pattern)
	{
		$this->pattern = $pattern;
	}

	/**
	 * <code>preg_match</code>
	 *
	 * @param string string match target string.
	 * @since 1.0 2006/11/02
	 */
	public function match($string)
	{
		$matchies = array();
		$ret = preg_match($this->pattern, $string, $matchies);
		$this->_matchies = new ArrayList($matchies);
		return $ret;
	}

	/**
	 * <code>preg_match_all</code>
	 *
	 * @param string $string match target string.
	 * @param string $option
	 * @since 1.0 2006/11/02
	 */
	public function matchAll($string, $option = PREG_SET_ORDER)
	{
		$matchies = array();
		$ret = preg_match_all($this->pattern, $string, $matchies, $option);
		$this->_matchies = new ArrayList($matchies);
		return $ret;
	}

	/**
	 *
	 * @param string $string
	 * @param string $dist_pattern
	 * @param int $limit = -1
	 * @since 1.0 2006/11/02
	 */
	public function replace($string, $dist_pattern, $limit = -1)
	{
		return preg_replace($this->pattern, $dist_pattern, $string, $limit);
	}

	/**
	 *
	 * @param string $string
	 * @param string $dist_pattern
	 * @param int $limit = -1
	 * @return ArrayList
	 * @since 1.0 2006/11/02
	 */
	public function split($string, $limit = -1)
	{
		return new ArrayList(preg_split($this->pattern, $string, $limit));
	}

	/**
	 * <p>preg_replace_callback</p>
	 *
	 * @param string $string
	 * @param string $dist_pattern
	 * @param int $limit = -1
	 * @since 1.0 2006/11/02
	 */
	public function replaceCallback($string, $callback, $limit = -1)
	{
		return preg_replace_callback($this->pattern, $callback, $string, $limit);
	}

	/**
	 * Return maties parameters.
	 *
	 * @return ArrayList
	 * @since 1.0 2006/11/02
	 */
	public function &getMatchies()
	{
		return $this->_matchies;
	}

	/**
	 * Return maties parameters.
	 *
	 * @return ArrayList
	 * @since 1.0 2006/11/02
	 */
	public function getMatch($i)
	{
		return $this->_matchies->get($i);
	}
	//
	//	/**
	//	 * @param object obj
	//	 * @since 1.0 2006/11/02
	//	 */
	//	public function setRuntimeArgs(&$obj)
	//	{
	//		$this->_runtime_args = $obj;
	//	}
}

class CacheManager
{
	const DEFAULT_STORAGE = 'barge\utils\FileCacheStorage';
	/**
	 * @var CacheManager
	 */
	private static $instance = null;

	/**
	 * @var CacheStorage
	 */
	private $storage;

	/**
	 * @return CacheManager
	 */
	public static function getInstance()
	{
		if (self::$instance === null) {
			self::$instance = new self();
		}
		return self::$instance;
	}
	private function __construct()
	{
		$className = System::getProperty('cache/class', self::DEFAULT_STORAGE);
		$storageClass = new ReflectionClass($className);
		$this->storage = $storageClass->newInstance(System::getProperty('cache/'));
	}

	/**
	 * @param string $id
	 * @param mixed $var
	 * @param long $ttl
	 */
	public function save($id, $var, $ttl = null)
	{
		$this->storage->save($id, $var, $ttl);
	}

	/**
	 * @param string $id
	 * @return {object}
	 */
	public function load($id)
	{
		return $this->storage->load($id);
	}

	/**
	 * @param string $id
	 */
	public function clear($id)
	{
		$this->storage->clear($id);
	}
}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.2 2008/11/07
 */
interface CacheStorage
{
	public function __construct(Config $config = null);
	public function save($id, $obj, $ttl = null);
	public function load($id);
	public function clear($id);
}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2008/11/07
 * @see CacheStorage
 */
class FileCacheStorage implements CacheStorage
{
	/**
	 * @var string
	 */
	private $filepath = null;

	/**
	 * @var string
	 */
	private $cache = null;

	/**
	 * @param Config $config
	 */
	public function __construct(Config $config = null)
	{
		$this->filepath = System::getWorkDir().'/cache.tmp';
	}

	/**
	 * @param $id
	 * @param $obj
	 * @param $ttl
	 */
	public function save($id, $obj, $ttl = null)
	{
		$array = array();
		if (file_exists($this->filepath)) {
			$array = unserialize(file_get_contents($this->filepath));
		}
		$array[$id] = $obj;
		file_put_contents($this->filepath, serialize($array));
		$this->cache = null;
	}
	public function load($id)
	{
		if ($this->cache !== null) {
			return (isset($this->cache[$id]) ? $this->cache[$id] : null);
		}
		if (!File::exists($this->filepath)) {
			return null;
		}
		$this->cache = unserialize(file_get_contents($this->filepath));
		return (isset($this->cache[$id]) ? $this->cache[$id] : null);
	}
	public function clear($id)
	{
		//		if ($this->cache == null) {
		//			$this->cache = array();
		//		}
		//		$this->cache[$id] = $obj;
		//		file_put_contents($this->filepath, serialize($this->cache));
	}
}


/**
 * ワイルドカードパターン
 * <p/>
 * アスターリスクおよび、クエッションをメタ文字として、パターンマッチング
 * および置換をサポートしています
 *
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.2 2009/01/25
 *
 */
class Wildcard
{

	/**
	 * Regexp pattern.
	 * @var string
	 */
	private $pattern = null;

	/**
	 * ワイルドカード文字のエスケープ
	 *
	 * @param string $string
	 * @return string
	 */
	public static function escape($string)
	{
		if ($string === null)
			return null;

		$escapes = '';
		$len = strlen($string);
		for ($i = 0; $i < $len; $i++) {
			switch ($string[$i]) {
			case '?':
			case '*':
				$escapes .= '\\'.$string[$i];
				break;
			default:
				$escapes .= $string[$i];
				break;
			}
		}
		return $escapes;
	}

	/**
	 * @param string $wildcard
	 * @param bool $caseIgnore 大文字、小文字を区別するかどうか
	 */
	public function __construct($wildcard, $caseIgnore = false)
	{
		$len = strlen($wildcard);
		$pattern = '';
		for ($i = 0; $i < $len; $i++) {
			switch ($wildcard[$i]) {
				case '*':
					$pattern .= '(.*?)';
					break;
				case '?':
					$pattern .= '(.)';
					break;
				case '.':
				case '-':
				case '$':
				case '(':
				case ')':
				case '+':
				case '|':
					$pattern .= '\\'.$wildcard[$i];
					break;
				default:
					$pattern .= $wildcard[$i];
					break;
			}
		}

		if ($caseIgnore) {
			$this->pattern = '|^'.$pattern.'$|i';
		} else {
			$this->pattern = '|^'.$pattern.'$|';
		}
	}

	/**
	 * <pre>
	 * // Find html files.
	 * $wildcard = new PathWildcard('*.html');
	 * echo $wildcard->match('src/sample.html');
	 *
	 * $wildcard = new PathWildcard('**\.html');
	 * echo $wildcard->match('index.html');
	 * echo $wildcard->match('src/index.html');
	 * </pre>
	 *
	 * @param string $path Path or filename.
	 * @return bool
	 */
	public function match($string)
	{
		return preg_match($this->pattern, $string) > 0;
	}

	/**
	 * <pre>
	 * // Backup html files.
	 * $wildcard = new PathWildcard('*.html');
	 * $wildcard->replace('src/sample.html', 'src/$1.html.back');
	 * </pre>
	 *
	 * @param string $path Path or filename.
	 * @param string $pattern 置換後のパターンを指定します。
	 * 				後方参照する場合、$N を指定することができ、それぞれＮ番目のパターンキャラクタ
	 * 				( *, ? ）でマッチした結果に置換されます。例えば検索パターンが *.html
	 * 				の場合、* にマッチングしたパターンを $1 というパターンで後方参照できます。
	 * @return string マッチした場合は置換後の文字列を返します。マッチしない場合は、<code>
	 * 				null</code>を返します。
	 */
	public function replace($string, $pattern)
	{
		$matches = array();
		$len = preg_match($this->pattern, $string, $matches);
		if ($len === 0)
			return null;
		return preg_replace('/\$(\\d+)/e', "isset(\$matches[\\1])?\$matches[\\1]:null", $pattern);
	}

	// *.html -> _$1.html
}


/**
 * ファイルパスに対するワイルドカードパターン
 * <p/>
 * 使用できるワイルドカードは、アスタリスク:* およびクエッション:? とパスデリミーターを意味
 * するスラッシュ :/ を使用できます。* および ? は任意の文字にマッチングしますが、パスデリミ
 * ターには、マッチングしません。パスのデリミターを含めて任意の文字を指定するには、** を使用
 * できます。
 *
 *
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.2 2008/11/08
 */
class PathWildcard
{
	//	const MATCH = 1;
	//	const UNMUTCH = 2;
	//	const NOMORE_SUBDIR = 3;

	/**
	 * @var bool
	 */
	private $fullPath = false;

	/**
	 * Regexp pattern.
	 * @var string
	 */
	private $pattern = null;

	/**
	 * @var string
	 */
	private $rootPath = null;

	/**
	 *
	 * @param string $wildcard
	 */
	public function __construct($wildcard)
	{
		$len = strlen($wildcard);

		if (strpos($wildcard, '/') !== false) {
			$this->fullPath = true;
		}

		// Find abstract path
		$temp = '';
		$this->rootPath = null;
		$exit = false;
		for ($i = 0; $i < $len && !$exit; $i++) {
			switch ($wildcard[$i]) {
				case '*':
				case '?':
					$exit = true;
					break;
				case '/':
					$this->rootPath.=$temp;
					$temp='/';
					break;
				default:
					$temp.=$wildcard[$i];
			}
		}

		$pattern = '';
		for ($i = 0; $i < $len; $i++) {
			switch ($wildcard[$i]) {
				case '*':
					if (isset($wildcard[$i+1]) && $wildcard[$i+1] === '*') {
						$pattern .= '(.*?)';
						$i++;
					} else {
						$pattern .= '([^\/]*)';
					}
					break;
				case '?':
					$pattern .= '([^\/])';
					break;
				case '.':
				case '-':
				case '$':
				case '(':
				case ')':
				case '+':
				case '|':
					$pattern .= '\\'.$wildcard[$i];
					break;
				default:
					$pattern .= $wildcard[$i];
					break;
			}
		}

		$this->pattern = '|^'.$pattern.'$|';
//		$log = barge\logging\LogFactory::getLogger();
//		$log->debug($this->rootPath);
//		$log->debug($this->pattern);
	}

	/**
	 * <pre>
	 * // Find html files.
	 * $wildcard = new PathWildcard('*.html');
	 * echo $wildcard->match('src/sample.html');
	 *
	 * $wildcard = new PathWildcard('**\.html');
	 * echo $wildcard->match('index.html');
	 * echo $wildcard->match('src/index.html');
	 * </pre>
	 *
	 * @param string $path Path or filename.
	 * @return bool
	 */
	public function match($path)
	{
		$path = str_replace('\\', '/', $path);
		if ($this->fullPath) {
			return preg_match($this->pattern, $path) > 0;
		} else {
			return preg_match($this->pattern, basename($path)) > 0;
		}
	}

	/**
	 *
	 * @param string $path
	 * @return bool|null パターンがマッチする場合は、<code>null</code>を返します。
	 */
	public function matchRoot($path)
	{
		$path = str_replace('\\', '/', $path);
		if ($this->fullPath) {
			if ($this->rootPath === null || strpos($path, $this->rootPath) === 0) {
				return (preg_match($this->pattern, $path) > 0);
			} else {
				//$log->debug('Skip : '.$path);
				return null;
			}
		} else {
			return (preg_match($this->pattern, basename($path)) > 0);
		}
	}

	/**
	 *
	 * <pre>
	 * // Backup html files.
	 * $wildcard = new PathWildcard('*.html');
	 * $wildcard->replace('src/sample.html', 'src/$1.html.back');
	 * </pre>
	 *
	 * @param string $path Path or filename.
	 * @param string $pattern 置換後のパターンを指定します。
	 * 				後方参照する場合、$N を指定することができ、それぞれＮ番目のパターンキャラクタ
	 * 				( *, ? ）でマッチした結果に置換されます。例えば検索パターンが *.html
	 * 				の場合、* にマッチングしたパターンを $1 というパターンで後方参照できます。
	 * @return string マッチした場合は置換後の文字列を返します。マッチしない場合は、<code>
	 * 				null</code>を返します。
	 */
	public function replace($path, $pattern)
	{
		$path = str_replace('\\', '/', $path);
		$matches = array();
		$len = 0;
		if ($this->fullPath) {
			$len = preg_match($this->pattern, $path, $matches);
		} else {
			$len = preg_match($this->pattern, basename($path), $matches);
		}
		if ($len === 0)
		return null;

		//		return preg_replace_callback('/\$(\\d+)/',
		//			function($offset) use ($matches) {
		//				return (isset($matches[$offset[1]]) ? $matches[$offset[1]] : '');
		//			}, $pattern);
		return preg_replace('/\$(\\d+)/e', "isset(\$matches[\\1])?\$matches[\\1]:null", $pattern);
	}

	// *.html -> _$1.html
}

class OutputBuffer
{
	/**
	 * @param Clousure $func
	 * @return unknown
	 */
	public static function capture($func)
	{
		try {
			ob_start();
			$func();
			return ob_get_clean();
		} catch (Exception $e) {
			ob_end_clean();
			throw $e;
		}
	}


}

/**
 *
 * @version 1.0 2009/01/31
 * @since Barge 1.2 2009/01/31
 */
class ObjectUtils
{
	/**
	 * @param mixed $obj
	 * @param string $format
	 * @param int $level
	 * @return string
	 */
	public static function toString($obj, $format= false, $level = 0)
	{
		$buf = new StringBuffer();
		if ($obj === null) {
			$buf->append('null');
		} else if (is_bool($obj)) {
			$buf->append($obj ? 'true' : 'false');
		} else if (is_string($obj)) {
			$buf->append('"'.$obj.'"');
		} else if (is_numeric($obj)) {
			$buf->append($obj);
		} else if (is_array($obj) || $obj instanceof HashMap) {
			$firstdone = false;
			$buf->append('[');
			if ($format)
				$buf->append("\n");
			foreach ($obj as $key => $value) {
				if ($firstdone) {
					$buf->append(',');
					if ($format)
						$buf->append("\n");
				}
				$buf->append($key.'=>'.self::toString($value, $format, $level+1));
				$firstdone = true;
			}
			$buf->append(']');
		} else if ($obj instanceof ArrayList) {
			$firstdone = false;
			$buf->append('[');
			if ($format)
				$buf->append("\n");
			foreach ($obj as $value) {
				if ($firstdone) {
					$buf->append(',');
					if ($format)
						$buf->append("\n");
				}
				$buf->append(self::toString($value, $format, $level+1));
				$firstdone = true;
			}
			$buf->append(']');
		} else {
			$descriptors = BeanUtils::getPropertyDescriptors($obj);
			$firstDone = false;
			$buf->append('[');
			if ($format)
				$buf->append("\n");
			foreach ($descriptors as $descriptor) {
				/* @var $descriptor PropertyDescriptor */
				if ($descriptor->isReadable() ) {
					if ($firstDone) {
						$buf->append(",");
						if ($format)
							$buf->append("\n");
					} else {
						$firstDone = true;
					}
					$buf->append($descriptor->getName());
					$buf->append(':');
					$value = $descriptor->getReader()->read($obj);
					$buf->append(self::toString($value, $format, $level+1));

				}
			}
			$buf->append(']');
		}
		return $buf->toString();
	}

	/**
	 * クラス名を取得する
	 *
	 * @param object $obj
	 * @return string
	 * @since 1.3 2009/04/09
	 */
	public static function getClass($obj)
	{
		return get_class($obj);
	}
}

//class Calendar
//{
//	const DAY_OF_WEEK = 10;
//
//	const MONDAY = 1;
//	const TUESDAY = 2;
//	const WEDNESDAY = 3;
//	const THURSDAY = 4;
//	const FRIDAY = 5;
//	const SATURDAY = 6;
//	const SUNDAY = 7;
//
//	public function __construct()
//	{
//	}
//}

class DateTerm
{
	/**
	 * @var TimeValue
	 */
	private $date1;

	/**
	 * @var TimeValue
	 */
	private $date2;

	/**
	 * コンストラクタ
	 *
	 * @param TimeValue $date1
	 * @param TimeValue $date2
	 */
	public function __construct(TimeValue $date1 = null, TimeValue $date2 = null)
	{
		$this->date1 = $date1;
		$this->date2 = $date2;
	}
}


/**
 * スレッド
 *
 * @version 1.0 2009/02/07
 * @since Barge 1.2 2009/02/07
 */
class Thread
{
	/**
	 * @param long $milisecond
	 * @since 1.0 2009/02/07
	 */
	public static function sleep($milisecond)
	{
		usleep($milisecond*1000);
	}
}

/**
 * Unicodeテーブル
 *
 * @version 1.0 2009/03/27
 * @see http://ja.wikipedia.org/wiki/Unicode
 * @see http://ash.jp/code/unitbl1.htm
 * @since Barge 1.2.2 2009/03/27
 */
class UnicodeSet
{
	/**
	 * 基本ラテン文字 （ASCII互換）、0 は除外
	 */
	const BASIC_LATIN = '[\x20-\x7f]';

	/**
	 * 日本語互換基本ラテン文字、基本ラテン文字に \:円マーク(YEN SIGN)、~:波ダッシュ(WAVE DASH)を加えたもの
	 */
	const BASIC_LATIN_JP = '[\x20-\x7e]|\xc2\xa5|\xe2\x80\xbe';

	/**
	 * 基本ラテン英数字、[0-9A-Za-z]
	 */
	const LATIN_ALPHA_NUMERIC = '[\x30-\x39\x41-\x5a\x61-\x7a]';

	/**
	 * 全角文字の半角変化形、半角ｶﾀｶﾅと句読点などの記号（ｱｶﾊﾟﾋﾞｰ｡､｢｣）
	 */
	const HALFWIDTH_FORMS_JP = '\xef(\xbd[\xa1-\xbf]|\xbe[\x80-\x9f])';

	/**
	 * 半角カタカナ濁音記号
	 */
	const HALFWIDTH_KATAKANA_DULL = '\xef\xbe\x9e';

	/**
	 * 半角カタカナ破裂音記号
	 */
	const HALFWIDTH_KATAKANA_PLOSIVE = '\xef\xbe\x9f';

	/**
	 * 全角カタカナ、[ア-ンヰヴァ-ォヵヶ]
	 */
	const KATAKANA = '\xe3(\x82[\xa1-\xbf]|\x83[\x80-\xb6])';

//	/**
//	 * \xE3\x83\xBC
//	 * 全角カタカナ、[ア-ンァ-ォヵヶー]
//	 */
//	const NATURE_KATAKANA = '\xe3(\x82[\xa1-\xbf]|\x83[\x80-b6])';

	/**
	 * 全角長音記号、カタカナ、ひらがなで使用される長音記号
	 */
	const FULLWIDTH_DASH = '\xe3\x83\xbc';

//	/**
//	 * 全角スペース
//	 */
//	const FULL_SPACE = '\xe3\x80\x80';
//
//	/**
//	 * 全角数字[０-９]
//	 */
//	const FULL_NUMERIC = '\xef\xbc[\x90-\x99]';
//
//	/**
//	 * 全角アルファベット[Ａ-Ｚ]
//	 */
//	const FULL_ALPHA_UPPER = '\xef\xbc[\xa1-\xba]';
//
//	/**
//	 * 全角アルファベット[ａ－ｚ]
//	 */
//	const FULL_ALPHA_LOWER = '\xef\xbd[\x81-\x9a]';
//
//	/**
//	 * 全角アルファベット[Ａ-Ｚａ－ｚ]
//	 */
//	const FULL_ALPHA = '\xef(\xbc[\xa1-\xba]|\xbd[\x81-\x9a])';
//
//
//
//	/**
//	 * 半角濁音記号
//	 */
//	const HALF_DULL = '\xef\xbe\x9e';
//
//	/**
//	 * 半角破裂音記号
//	 */
//	const HALF_PLOSIVE = '\xef\xbe\x9f';
//
//	/**
//	 * 半角カタカナ、カサタハ行
//	 */
//	private $HALFWIDTH_KATAKANA_LINES = array(
//		'k' => '\xef\xbd[\xb6-\xba]',
//		's' => '\xef\xbd[\xbb-\xbf]',
//		't' => '\xef\xbe[\x80-\x84]',
//		'h' => '\xef\xbe[\x8a-\x8e]',
//		);
//	/**
//	 * 半角スペース
//	 */
//	const HALF_SPACE = '\x20';
//
//	/**
//	 * 半角数字 [0-9]
//	 */
//	const HALF_NUMERIC = '[\x30-\x39]';

	/**
	 * 半角英字 [A-Za-z]
	 */
	const ALPHA = '[\x41-\x5a\x61-\x7a]';

	/**
	 * 半角英数字 [0-9A-Za-z]
	 */
	const ALPHANUMERIC = '[\x30-\x39\x41-\x5a\x61-\x7a]';

	/**
	 * @var string
	 */
	private $pattern;

	/**
	 * <pre>
	 * $unicode = new UnicodeSet(UnicodeTable::FULLWIDTH_SPACE, UnicodeTable::HALFWIDTH_SPACE);
	 * $unicode->macth('abc');
	 * </pre>
	 */
	public function __construct(/*...*/)
	{
		$this->pattern = func_get_args();
		$pattern = '/^';
		foreach (func_get_args() as $i => $set) {
			if ($i > 0) $pattern .= '|';
			$pattern .= '('.$set.')';
		}
		$pattern .= '$/';
		$this->pattern = $pattern;
	}

	/**
	 * @param string $value
	 */
	public function contains($value)
	{
//echo bin2hex($value)."\n";
		return preg_match($this->pattern, $value) > 0;
	}
}

/**
 * @version 1.0 2009/05/09
 * @since 1.2.2 2009/05/09
 * @see Exception
 */
class UnsupportedLocalException extends Exception
{
}

/**
 * ローカル文字列を扱うためのクラス
 *
 * @version 1.0 2009/03/28
 * @since 1.2.2 2009/03/28
 */
abstract class LocaleString
{
	/**
	 * @var LocalString
	 */
	private static $instance;

	/**
	 * 既知の実装リスト
	 * @var array
	 */
	private static $languages = array(
		'ja_JP' => 'barge\utils\LocaleString_ja_JP'
		);

	/**
	 * @return LocalString
	 * @throws UnsupportedLocalException
	 */
	public static function getInstance()
	{
		if (self::$instance === null) {
			$local = System::firstNotEmpty(System::getProperty('System/locale'), 'ja_JP');
			if (!isset(self::$languages[$local])) {
				$message = "Unsupported locale '{$local}'";
				throw new UnsupportedLocalException($message);
			}
			$classloader = System::getClassLoader();
			self::$instance = $classloader->newInstance(self::$languages[$local]);
		}
		return self::$instance;
	}

	/**
	 * 文字列幅を返します
	 * @param string $string
	 * @return int
	 */
	abstract public function width($string);

	/**
	 * 文字列がブランク（スペースセパレータ）かどうかを返します
	 * @param string $string
	 * @return bool
	 */
	abstract public function blanks($string);
}

/**
 * 日本語を扱うためのクラス
 * @version 1.0 2009/03/2
 * @since 1.2.2 2009/03/28
 */
class LocaleString_ja_JP
{
	/**
	 * @param string $value
	 * @return int
	 */
	public function width($value)
	{
		$unicode = new UnicodeSet(
			UnicodeSet::BASIC_LATIN_JP,
			UnicodeSet::HALFWIDTH_FORMS_JP);
		$width = 0;
		if ($value === null) {
			$width = 0;
		} else {
			$width = 0;
			$len = mb_strlen($value);
			for ($i = 0; $i < $len; $i++)
				$width += ($unicode->contains(mb_substr($value, $i, 1)) ? 1 : 2);
		}
		return $width;
	}

	/**
	 * @param string $value
	 * @return int
	 */
	public function blanks($value)
	{
		return ($value === null || $value === '' ||
			preg_match('/^\x20|(\xe3\x80\x80)$/', $value) > 0);
	}
}
